Slider
A ruler with a hand. The value is drawn as a dimension from zero with your number in italic in the gap, or runs through the label's spread letters, or makes the label as loud as the value.
Report an issue with SliderExample
Rest
Too short a gap
Focus
Disabled
spread, rest
spread, focus
dynamics, quiet
dynamics, loud
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/slider.jsonUsage
import { Slider } from "@/components/ui/slider"export default function Example() {return (<div className="grid max-w-[36rem] gap-y-9"><Slider label="Volume" defaultValue={64} /><Slider variant="spread" label="Brightness" defaultValue={58} unit="%" /><Slider variant="dynamics" label="Volume" defaultValue={40} /></div>)}Contract
- Underneath: native range, plus a hook for the readout.
- Anatomy:
.ot-ruler[data-variant="dimension | spread | dynamics"]holding a label, an<output>,.ot-ruler-hand(the<input type="range">, and for spread.ot-ruler-letters) and.ot-ruler-scaleof<span style="--n: 0…1">. Script sets--p(0–1). Spread and dynamics need a string label; they keep it in the DOM as.ot-srand draw itaria-hidden. - The track is a ruler: minor ticks every 5%, major every 25%, and the filled part in ink. The thumb is an ink hairline topped by the accent dot (
--htall). - Dimension (the default). Yours: the value, a large italic numeral set in the gap of a dimension line (after Paul Rand),
.ot-ruler-value. The line runs from zero to the thumb, with a tick at each end, and always stays true to the hand: when the gap is too short for the figure, script setsdata-outsideand the figure stands outside, past the far tick, as a draughtsman sets it. - Spread. After "Renaissance.", whose words are spread to the edges: the label's letters, in capitals, are the ruler's marks, set out across its whole length over a single baseline (
--h: 64px). The hand stands through them and inks every letter it has passed, splitting the one it stands in, as "the uncreative" splits its word (background-clip: text). Your number,.ot-ruler-corner, waits in the far corner; the scale keeps only its two ends. - Dynamics. The score marks loudness pp to ff in italic under the notes. Here the label,
.ot-ruler-loud, is as loud as the value: its weight runs 100 to 900 and its width 62% to 125% with the hand (as far as the pair's voice allows), with your number after it; its size is set from its letter count and the ruler's width (a size container), so at its heaviest it still fits one line and nothing reflows while you drag. The scale reads pp p mp mf f ff as.ot-term, and the nearest marking inks (data-on). - The value and unit are one text node, isolated left to right; a shorter number never shifts a separate unit node. The ruler and the figure's placement still follow the page direction.
- While the thumb is held (
:active), the value lifts 5px, and it drops back with spiccato when you let go. - States: rest, focus (outline 8px out), disabled (45% opacity). Right to left, zero is on the right, the fill, the ink through the letters and the outside figure follow. Forced colours: the ruler restates its tokens in system colours (
CanvasText,GrayText,Highlightfor the dot). - Keyboard: arrows step, Page Up / Page Down jump, Home / End go to the ends.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
label | ReactNode | What is being measured. It labels the range. spread and dynamics draw it, so pass a string there. | |
variant | "dimension" | "spread" | "dynamics" | "dimension" | How the value is drawn. dimension: a dimension line from zero to the hand with your number in its gap, and past the far tick when the gap is too short for it. spread: the label's letters, in capitals, are the ruler's marks, spread across its length; the hand inks every letter it passes, splitting the one it stands in, and your number waits in the far corner. dynamics: the label is as loud as the value, its weight and width following the hand, over a scale from pp to ff whose nearest marking inks. |
min / max / step | number | 0 / 100 / 1 | The range. Ticks fall every 5%, labelled every 25%. |
value / defaultValue | number | The current value. | |
onValueChange | (value: number) => void | Called with the new value as the hand moves. | |
unit | string | Set after the number as written: " kg" with its space, "%" without. | |
disabled | boolean | false | The ruler fades and the hand stays put. |
className | string | Classes for the ruler's outer div; the input props apply to the native range inside it. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Slider | Paul Rand dimension lines | Value set in a dimension from zero |
| Spread slider | "Renaissance."; "the uncreative" | The label's letters are the ruler's marks; the hand inks them |
| Dynamics slider | The score's dynamics | The label is as loud as the value |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Slider | Lift | The value rises while the hand is on it |
| Spread slider | Ink follows | The ink runs through the letters with the hand |
| Dynamics slider | Swell | The word grows heavier and wider with the hand |