Slider
A single value or a range of two. Arrow keys and a press on the track give the same result as every drag.
client - size not measured yet
Slider
client - size not measured yetA single value or a range of two. Arrow keys and a press on the track give the same result as every drag.
Try it
size
invalid
fullWidth
showValue
min
max
step
largeStep
minStepsBetweenValues
The code
slider.tsx
import { Slider } from 'topclassui/slider'
<div className="grid justify-items-start gap-2">
<span id="slider-playground-label" className="text-sm text-body">Volume</span>
<Slider aria-labelledby="slider-playground-label" defaultValue={60} />
</div>A hidden range input carries the value, so the slider submits with its form; the thumb moves once the page hydrates.
Every state
4 statesImport from the package
topclassui/slider
import { Slider } from 'topclassui/slider'Install once:npm i topclassui@beta zod
Examples
- For a number picked by feel inside known limits. For an exact number, a NumberField.
min,maxandstepare 0, 100 and 1, clamped to plus or minus 1e15;largeStepis never under thestep, so Page Up always moves.minStepsBetweenValueskeeps the two thumbs of a range apart; they never cross.showValuewrites the value after the track, in the provider's locale and digits. A long value moves below the track.- The width is an Input's of the same
size;fullWidthfills the parent. A slider always has a value, sorequiredis accepted and dropped.
basic
Volume
basic.tsx
import { Slider } from 'topclassui/slider'
/** Outside a Field: the slider is named by the visible text above it. Drag the thumb, press the track, or use the arrows. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-2">
<span id="slider-basic-label" className="text-sm text-body">Volume</span>
<Slider aria-labelledby="slider-basic-label" defaultValue={60} step={5} />
</div>
)
}
range
Price
range.tsx
import { Slider } from 'topclassui/slider'
/** A pair gives two thumbs, named Minimum and Maximum, that cannot cross. The form receives the name twice. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-2">
<span id="slider-range-label" className="text-sm text-body">Price</span>
<Slider aria-labelledby="slider-range-label" name="price" defaultValue={[20, 80]} step={5} />
</div>
)
}
show-value
show-value.tsx
import { Slider } from 'topclassui/slider'
/** showValue writes the value after the track, in the provider's digits; format shapes it, here as dollars. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-4">
<Slider aria-label="Brightness" defaultValue={40} showValue />
<Slider
aria-label="Budget"
defaultValue={[200, 800]}
min={0}
max={1000}
step={50}
format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
showValue
/>
</div>
)
}
form
form.tsx
import { Button } from 'topclassui/button'
import { Slider } from 'topclassui/slider'
/** The slider carries the name and the default: it sends volume=60, before any script runs and after. */
export default function Demo() {
return (
<form className="grid justify-items-start gap-3">
<span id="slider-form-label" className="text-sm text-body">Volume</span>
<Slider aria-labelledby="slider-form-label" name="volume" defaultValue={60} step={5} />
<Button type="submit">Save</Button>
</form>
)
}
sizes
sizes.tsx
import { Slider } from 'topclassui/slider'
/** The five sizes. Each is as wide as an Input of its size, and each thumb stays 24 px, and 44 px under a finger. */
export default function Demo() {
return (
<div className="grid justify-items-start gap-4">
<Slider size="xs" aria-label="Extra small" defaultValue={20} />
<Slider size="sm" aria-label="Small" defaultValue={40} />
<Slider size="md" aria-label="Medium" defaultValue={50} />
<Slider size="lg" aria-label="Large" defaultValue={60} />
<Slider size="xl" aria-label="Extra large" defaultValue={80} />
</div>
)
}
In Arabic, right to left
مستوى الصوتنطاق السعر
rtl.tsx
import { TopClassProvider } from 'topclassui/provider'
import { Slider } from 'topclassui/slider'
/** Arabic, inside TopClassProvider as the root layout would mount it. The track runs from right to left: 30 sits 30%
* in from the right, ArrowLeft raises the value, and the two thumbs of the range are named in Arabic. */
export default function Demo() {
return (
<TopClassProvider direction="rtl" locale="ar">
<div dir="rtl" lang="ar" className="grid justify-items-start gap-3">
<span id="slider-rtl-volume" className="text-sm text-body">مستوى الصوت</span>
<Slider aria-labelledby="slider-rtl-volume" name="volume" defaultValue={30} showValue />
<span id="slider-rtl-price" className="text-sm text-body">نطاق السعر</span>
<Slider aria-labelledby="slider-rtl-price" name="price" defaultValue={[20, 80]} />
</div>
</TopClassProvider>
)
}
Props
Keyboard and accessibility
- Name it with
aria-labeloraria-labelledby: what takes the focus is a range input inside the thumb. The two thumbs of a range are named Minimum and Maximum. - Every drag has a keyboard path (WCAG 2.5.7). The thumb is the target: 24px, 44px under a touch pointer.
invalidsets aria-invalid on the group and on each range input; the fill, the knob and the line around the track turn to danger.
Right to left
Under a right-to-left TopClassProvider the track runs from the right: ArrowLeft adds a step, and a value of 30 sits 30% in from the right.
Server and client
A client component: it renders on the server and hydrates in the browser.
Files: index.tsx, client.tsx.
On the chip: client - size not measured yet.