# Slider stops

A slider over a few named stops, "Low", "Medium", "High", in place of numbers: for a setting whose values are ordered words (effort, detail, autonomy). The thumb snaps to the stops, reads as the chosen stop's name (not its position), and every stop's name is shown under the track at its place; clicking a name moves the thumb there. The names aren't extra tab stops: the thumb reads them.

Nothing is pre-selected: with `value` `null` (or no `defaultValue`) the thumb waits at the start, drawn hollow, and reads "Not chosen" until the person moves it, presses a stop's name, or presses Home there. Right to left, the stops run from the right and the arrow keys follow them.

- Registry name: `slider-stops` (`@candor/slider-stops`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/slider-stops

## Install

```bash
npx candor-ui add slider-stops
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/slider-stops`.
It brings in: bidi, locale, slider, tokens, utils.
npm packages: @base-ui/react@^1.9.0, lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { SliderStops } from "@/components/candor/ui/slider-stops"

const efforts = [
  { value: "low", label: "Low" },
  { value: "medium", label: "Medium" },
  { value: "high", label: "High" },
  { value: "max", label: "Maximum" },
]

// Named stops in place of numbers. Nothing is chosen at first: the thumb waits, hollow, and reads "Not chosen" until
// the person moves it or presses a name. The visible words are the start of the thumb's name, `label`.
export default function Example() {
  const [effort, setEffort] = React.useState<string | null>(null)
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <span aria-hidden className="text-sm font-medium">
        Effort
      </span>
      <SliderStops label="Effort" stops={efforts} value={effort} onValueChange={setEffort} />
    </div>
  )
}
```

## <SliderStops>

Props:

- `stops: SliderStop[]`: The stops in order, two or more.
- `value?: string | null | undefined`: The chosen stop's value, when the app controls it; `null` while nothing is chosen. A value that is none of the stops' values leaves the thumb unchosen at the start, and the thumb reads that value as it is, not "Not chosen".
- `defaultValue?: string | undefined`: The stop chosen at first, when the slider keeps its own state. Leave it out so nothing is chosen for the person.
- `onValueChange?: ((value: string) => void) | undefined`: Called with the value of the stop the person chooses.
- `label: string`: The thumb's name, what the setting is: "Effort".
- `locked?: string | null | undefined`: Why the setting can't be changed right now. The thumb stays focusable, ignores the pointer and keys, and says why.
- `className?: string | undefined`: Classes for the outer box.

## Strings (`sliderStops`)

The fixed words, English by default. Override any of them under `sliderStops` in `<LocaleProvider strings={{ sliderStops: { … } }}>`; the table is exported as `sliderStopsStrings`.

- `notChosen`: Not chosen

## Types

### SliderStop

One named stop on a `SliderStops`: the value the app gets and the word the person reads.

```ts
{
  /** The value reported when this stop is chosen. */
  value: string;
  /** The stop's name, shown under the track and read as the thumb's value: "Medium". */
  label: string;
}
```

## Motion

- **tint** (tint): The thumb's ring fades in when the pointer is over it and when it takes keyboard focus, and it fills when a stop is chosen.

Set with `--cd-motion-slider-stops-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
