# Slider

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/slider/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A value set roughly between two bounds (range slider): a share of traffic, an alert threshold, weekly hours. The position on the track says as much as the figure.

```ts
import { Slider } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/slider/

`onChange` reports a number already on the step and inside the bounds, by pointer and by key alike. Pair it with a [NumberInput](https://romanhaendler.github.io/umriss-ui/core/numberinput/) on the same state when the exact figure matters too.

## Examples

### Slider

Use a slider for a value set roughly between two bounds; `step` sets how finely it moves.

```tsx
import { FormField, Slider } from "@umriss-ui/core";

export default function CanaryTraffic() {
  return (
    <FormField label="Canary traffic, %" hint="Share of requests the new release receives." style={{ maxWidth: 360 }}>
      <Slider min={0} max={100} step={5} defaultValue={10} />
    </FormField>
  );
}
```

### Units and marks

`format` writes the value for the readout and the screen reader; `marks` show reference values, with a word where one has a `label`.

```tsx
import { FormField, Grid, Slider } from "@umriss-ui/core";

export default function UnitsAndMarks() {
  return (
    <Grid minItemWidth="260px" gap={6}>
      <FormField label="Alert above error rate">
        <Slider
          min={0}
          max={5}
          step={0.1}
          defaultValue={1.5}
          format={(v) => `${v.toFixed(1)} %`}
          marks={[0, { value: 1, label: "Objective" }, 5]}
        />
      </FormField>
      <FormField label="Log retention" hint="30 days, fixed by the data protection policy.">
        <Slider min={7} max={90} defaultValue={30} disabled showValue={false} marks={[7, 30, 90]} />
      </FormField>
    </Grid>
  );
}
```

### Set roughly, then exactly

Control it with `value` and `onChange` and put a `NumberInput` beside it on the same state for the exact figure.

```tsx
import { useState } from "react";
import { FormField, NumberInput, Slider, Stack } from "@umriss-ui/core";

export default function SetRoughlyThenExactly() {
  const [hours, setHours] = useState(32);

  return (
    <Stack gap={3} style={{ maxWidth: 420 }}>
      <FormField label="Weekly capacity, Kofi Mensah">
        <Slider
          min={0}
          max={48}
          value={hours}
          onChange={setHours}
          showValue={false}
          format={(v) => `${v} h`}
          marks={[{ value: 0, label: "0" }, { value: 40, label: "Full time" }, { value: 48, label: "48" }]}
        />
      </FormField>
      <FormField label="Exact hours">
        <NumberInput value={hours} onChange={(v) => setHours(v ?? 0)} min={0} max={48} decimals={0} suffix="h" />
      </FormField>
    </Stack>
  );
}
```

### Split a budget

Several sliders share one whole; show the sum they make in words, since no single slider can say it.

```tsx
import { useState } from "react";
import { Badge, Card, CardBody, CardHeader, FormField, Slider, Stack, Text, useFormats } from "@umriss-ui/core";

const CHANNELS = [
  { id: "search", label: "Search ads", plan: 45 },
  { id: "events", label: "Events", plan: 30 },
  { id: "content", label: "Content", plan: 25 },
];

const BUDGET = 68_000;

export default function SplitABudget() {
  const formats = useFormats();
  const [shares, setShares] = useState<Record<string, number>>({ search: 45, events: 35, content: 25 });
  const sum = CHANNELS.reduce((total, c) => total + (shares[c.id] ?? 0), 0);

  return (
    <Card style={{ maxWidth: 560 }}>
      <CardHeader
        eyebrow="CC-1200 Marketing · April"
        title={`Budget ${formats.number(BUDGET, 0)} €`}
        actions={<Badge tone={sum === 100 ? "success" : "warning"}>{sum === 100 ? "Adds up to 100 %" : `Sum ${sum} %`}</Badge>}
      />
      <CardBody>
        <Stack gap={5}>
          {CHANNELS.map((c) => (
            <FormField key={c.id} label={c.label}>
              <Slider
                step={5}
                value={shares[c.id] ?? 0}
                onChange={(v) => setShares((previous) => ({ ...previous, [c.id]: v }))}
                format={(v) => `${v} % · ${formats.number((BUDGET * v) / 100, 0)} €`}
                marks={[0, 50, 100, { value: c.plan, label: "Plan" }]}
              />
            </FormField>
          ))}
          <Text size="xs" tone="muted">
            The plan was agreed in the March review.
          </Text>
        </Stack>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- An exact figure, or a range without sensible bounds → NumberInput

## Keyboard

| Key | Action |
|---|---|
| `Arrow Right / Arrow Up` | Increases the value by one step. |
| `Arrow Left / Arrow Down` | Decreases the value by one step. |
| `Page Up / Page Down` | Moves by a tenth of the range. |
| `Home / End` | Moves to the lower or upper bound. |

## API

### `SliderProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `defaultValue` | `number` | `min` | Uncontrolled: the value to start at.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/slider/#configurator-slider), [Slider](https://romanhaendler.github.io/umriss-ui/core/slider/#canary-traffic), [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks) |
| `value` | `number` | — | Controlled: the value.<br>Shown in: [Set roughly, then exactly](https://romanhaendler.github.io/umriss-ui/core/slider/#set-roughly-then-exactly), [Split a budget](https://romanhaendler.github.io/umriss-ui/core/slider/#split-a-budget), [Browse down and back up (Breadcrumb)](https://romanhaendler.github.io/umriss-ui/core/breadcrumb/#browse-down-and-back-up) and 2 more |
| `min` | `number` | `0` | The smallest value.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/slider/#configurator-slider), [Slider](https://romanhaendler.github.io/umriss-ui/core/slider/#canary-traffic), [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks) and 4 more |
| `max` | `number` | `100` | The largest value.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/slider/#configurator-slider), [Slider](https://romanhaendler.github.io/umriss-ui/core/slider/#canary-traffic), [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks) and 4 more |
| `step` | `number` | `1` | The step the value moves by, by pointer or key.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/slider/#configurator-slider), [Slider](https://romanhaendler.github.io/umriss-ui/core/slider/#canary-traffic), [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks) and 3 more |
| `marks` | `readonly `[`SliderMark`](https://romanhaendler.github.io/umriss-ui/core/slider/#type-SliderMark)`[]` | — | Values marked on the track; an object with a `label` writes the word beneath its mark. A mark shows a value, it does not catch the thumb.<br>Shown in: [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks), [Set roughly, then exactly](https://romanhaendler.github.io/umriss-ui/core/slider/#set-roughly-then-exactly), [Split a budget](https://romanhaendler.github.io/umriss-ui/core/slider/#split-a-budget) and 1 more |
| `format` | `(value: number) => string` | the number in the formats' notation, with as many decimals as the step has | The value as text: for the readout and for the screen reader (`aria-valuetext`).<br>Shown in: [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks), [Set roughly, then exactly](https://romanhaendler.github.io/umriss-ui/core/slider/#set-roughly-then-exactly), [Split a budget](https://romanhaendler.github.io/umriss-ui/core/slider/#split-a-budget) and 3 more |
| `showValue` | `boolean` | `true` | The value in mono beside the track.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/slider/#configurator-slider), [Units and marks](https://romanhaendler.github.io/umriss-ui/core/slider/#units-and-marks), [Set roughly, then exactly](https://romanhaendler.github.io/umriss-ui/core/slider/#set-roughly-then-exactly) and 1 more |
| `ref` | `Ref<HTMLInputElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onChange` | `(value: number) => void` | — | Reports every change, by pointer or key, as a number on the step.<br>Shown in: [Set roughly, then exactly](https://romanhaendler.github.io/umriss-ui/core/slider/#set-roughly-then-exactly), [Split a budget](https://romanhaendler.github.io/umriss-ui/core/slider/#split-a-budget), [Browse down and back up (Breadcrumb)](https://romanhaendler.github.io/umriss-ui/core/breadcrumb/#browse-down-and-back-up) and 2 more |

Also takes every attribute of `<input>` – without `defaultValue`, `max`, `min`, `onChange`, `step`, `type`, `value`.

### Types on this page

#### `SliderMark`

A mark on the track: a value, or a value with the word beneath it.

```ts
type SliderMark = number | { value: number; label?: ReactNode };
```

## Known limits

- One thumb: no range with two ends.
- Marks show values; they do not catch the thumb.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
