# Slider marks

A slider with marks along its track, values it is pulled to, and its value above the thumb while it moves: for a setting with a few values that matter (a default, a recommended level, the plan's limit) among many it could take.

Marks are ticks (with a word under each that has a `label`); a mark the value has passed takes the accent colour, and in forced colours it is a filled shape. `detents` are values the thumb is pulled to by the pointer: within `snap` of one, the value becomes it exactly, never a value between. The keys step as usual, by `step`, so every value stays reachable. Dragged past an end, the track stretches a little toward the pointer while the thumb and the value stay at the end, and springs back when the person lets go.

The readout above the thumb is for the eye (hidden from screen readers, which hear the thumb's value in `format`'s words). Name the thumb with `label`; right to left, the track runs from the right and the arrow keys follow it.

A mark's word is also read with the thumb's value when the thumb sits on it: "1.0, Default".

- Registry name: `slider-marks` (`@candor/slider-marks`)
- 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-marks

## Install

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

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/slider-marks`.
It brings in: locale, motion, 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 { SliderMarks } from "@/components/candor/ui/slider-marks"

// Marks show the values that matter; the pointer is pulled to the default and the recommended level, and the keys
// still step by 0.1 so every value is reachable. The value shows above the thumb while it moves.
export default function Example() {
  const [temperature, setTemperature] = React.useState(0.7)
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-baseline justify-between text-sm">
        <span aria-hidden className="font-medium">
          Temperature
        </span>
        <span className="text-muted-foreground tabular-nums">{temperature.toFixed(1)}</span>
      </div>
      <SliderMarks
        label="Temperature"
        min={0}
        max={2}
        step={0.1}
        value={temperature}
        onValueChange={setTemperature}
        marks={[{ value: 0, label: "Exact" }, { value: 1, label: "Default" }, { value: 2, label: "Loose" }]}
        detents={[0.7, 1]}
        format={(n) => n.toFixed(1)}
      />
    </div>
  )
}
```

## <SliderMarks>

Props:

- `value?: number | undefined`: The value, when the app controls it.
- `defaultValue?: number | undefined`: The value at first, when the slider keeps its own state.
- `onValueChange?: ((value: number) => void) | undefined`: Called with the value as the person moves the thumb: a detent's value exactly when the thumb is pulled to one.
- `onValueCommit?: ((value: number) => void) | undefined`: Called with the value when the person lets go, or after a key press: the value to act on.
- `min?: number | undefined` (default `0`): The lowest value.
- `max?: number | undefined` (default `100`): The highest value.
- `step?: number | undefined` (default `1`): How far one arrow key moves the value.
- `label: string`: The thumb's name, what the setting is: "Temperature".
- `marks?: SliderMark[] | undefined` (default `[]`): Marks along the track: a tick at each value, and its word under it when it has one.
- `detents?: number[] | undefined` (default `[]`): Values the thumb is pulled to by the pointer, within `snap`.
- `snap?: number | undefined`: How near a detent the pointer pulls the value to it, in the slider's units. Defaults to 2% of the range.
- `readout?: "always" | "drag" | "never" | undefined` (default `"drag"`): When the value shows above the thumb: `always`, only while it is dragged or focused (`drag`), or `never`.
- `format?: ((value: number) => string) | undefined`: The value in words, for the readout and screen readers: `(n) => `${n} °C``. Defaults to the locale's number.
- `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 (`sliderMarks`)

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

- `valueAtMark`: {value}, {mark}

## Types

### SliderMark

One mark on a `SliderMarks` track: a value, and a word under it if it has one.

```ts
{
  /** Where the mark sits, in the slider's units. */
  value: number;
  /** The word under the mark: "Default", "Recommended". Left out, the mark is a tick alone. */
  label?: string | undefined;
}
```

## Motion

- **mark** (tint): A mark takes the accent colour as the value passes it.
- **release** (move): The track springs back from a stretch past its end when the person lets go; none under calm.

Set with `--cd-motion-slider-marks-<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`.
