Slider
Pick a number or a range by dragging — one or two thumbs, marks, a value bubble and Field integration.
'use client';
import { Slider } from '@akhatmirzo/ui';
export default function SliderBasic() {
return (
<div className="w-full max-w-sm">
<Slider defaultValue={[40]} aria-label="Yorqinlik" />
</div>
);
}
Import
import { Slider } from '@akhatmirzo/ui';When to use
- When the exact number doesn't matter — volume, brightness, radius. Users pick "roughly" and see the result right away.
- Range filters — price "from — to", age, area. A two-thumb slider is faster than two inputs.
- When users need an exact value (an amount, a quantity), an
InputorMaskedInputworks better — or put one next to the slider. - With only a few steps (3–5 options), a
SegmentedControlor aRadioGroupis clearer.
The value is always an array
Like Radix, the value is always number[]: one thumb — [50], a range — [20, 80]. The number of
thumbs comes from the array length. Without defaultValue the slider starts at [min].
<Slider defaultValue={[50]} />
<Slider defaultValue={[20, 80]} />Range
Two values — two thumbs. formatValue formats the value in the bubble and for screen readers;
minStepsBetweenThumbs sets the minimum distance between thumbs (in steps).
Narx: 1 000 000 so'm — 3 500 000 so'm
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { Slider } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function SliderRange() {
const [price, setPrice] = useState([1_000_000, 3_500_000]);
return (
<div className="flex w-full max-w-sm flex-col gap-4">
<Slider
value={price}
onValueChange={setPrice}
min={0}
max={5_000_000}
step={50_000}
minStepsBetweenThumbs={2}
formatValue={(v) => formatMoney(v)}
aria-label="Narx"
/>
<p className="m-0 text-sm text-text-muted">
Narx:{' '}
<span className="font-semibold text-text tabular-nums">
{formatMoney(price[0]!)} — {formatMoney(price[1]!)}
</span>
</p>
</div>
);
}
Value bubble
showValue controls the value shown above the thumb (styled like a Tooltip):
| Value | Visible when |
|---|---|
active (default) | Hovered, dragged or focused |
always | Always |
never | Never |
'use client';
import { Slider } from '@akhatmirzo/ui';
export default function SliderShowValue() {
return (
<div className="flex w-full max-w-sm flex-col gap-10 pt-8">
<Slider defaultValue={[30]} showValue="active" aria-label="Faol paytda" />
<Slider defaultValue={[60]} showValue="always" aria-label="Doim" />
<Slider defaultValue={[80]} showValue="never" aria-label="Hech qachon" />
</div>
);
}
Marks
marks draws ticks and labels under the track. Marks are visual only — step is what limits the
value.
'use client';
import { Slider } from '@akhatmirzo/ui';
const marks = [
{ value: 0, label: '0%' },
{ value: 25, label: '25%' },
{ value: 50, label: '50%' },
{ value: 75, label: '75%' },
{ value: 100, label: '100%' },
];
export default function SliderMarks() {
return (
<div className="w-full max-w-sm">
<Slider
defaultValue={[25]}
step={25}
marks={marks}
formatValue={(v) => `${v}%`}
aria-label="Chegirma"
/>
</div>
);
}
Sizes and states
size — md (6px track, 20px thumb, default) or sm (4px / 16px). The thumb's hit area is at
least 40px in both sizes. disabled blocks dragging and focus.
'use client';
import { Slider } from '@akhatmirzo/ui';
export default function SliderSizes() {
return (
<div className="flex w-full max-w-sm flex-col gap-8">
<Slider size="sm" defaultValue={[35]} aria-label="Kichik" />
<Slider size="md" defaultValue={[65]} aria-label="O'rta" />
<Slider defaultValue={[50]} disabled aria-label="O'chirilgan" />
</div>
);
}
With Field
Inside a Field the label names the thumb, the description and error are linked
through aria-describedby, and an error switches the slider to the invalid state (red thumb and
fill).
Do'kondan necha km uzoqlikkacha yetkazasiz
40 km dan uzoqqa kuryer yo'q
'use client';
import { Field, Slider } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function SliderWithField() {
const [km, setKm] = useState([45]);
return (
<div className="w-full max-w-sm">
<Field
label="Yetkazib berish radiusi"
description="Do'kondan necha km uzoqlikkacha yetkazasiz"
error={km[0]! > 40 ? "40 km dan uzoqqa kuryer yo'q" : undefined}
>
<Slider value={km} onValueChange={setKm} max={60} formatValue={(v) => `${v} km`} />
</Field>
</div>
);
}
Vertical
orientation="vertical" makes the slider fill its parent's height (at least 160px). ↑ ↓ work on
the keyboard too.
'use client';
import { Slider } from '@akhatmirzo/ui';
export default function SliderVertical() {
return (
<div className="flex h-48 items-stretch gap-10 pl-12">
<Slider orientation="vertical" defaultValue={[70]} aria-label="Ovoz" />
<Slider orientation="vertical" defaultValue={[20, 60]} aria-label="Harorat" />
</div>
);
}
Forms and APIs
- With
name, each thumb renders a hidden<input>inside a<form>:namefor one thumb,name[]for a range. onValueChangefires on every move;onValueCommitfires once when dragging ends (or on a keyboard change). Hook server requests toonValueCommit.
<Slider
defaultValue={[20]}
onValueChange={setPreview} // on every move
onValueCommit={(v) => saveSettings({ volume: v[0] })} // on release
/>Props
Other <span> attributes and Radix Root props (dir, form) are passed through; ref points to
the root element.
| Prop | Type | Default | Description |
|---|---|---|---|
value | number[] | — | Value (controlled mode) |
defaultValue | number[] | [min] | Initial value |
onValueChange | (value: number[]) => void | — | On every change |
onValueCommit | (value: number[]) => void | — | When dragging ends or on a keyboard change |
min | number | 0 | Minimum value |
max | number | 100 | Maximum value |
step | number | 1 | Step |
minStepsBetweenThumbs | number | 0 | Minimum distance between thumbs, in steps |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Direction |
size | 'sm' | 'md' | 'md' | Track 4 / 6px, thumb 16 / 20px |
showValue | 'never' | 'active' | 'always' | 'active' | Value bubble above the thumb |
formatValue | (value: number) => string | String | Text for the bubble and aria-valuetext |
marks | { value: number; label?: ReactNode }[] | — | Marks under the track |
disabled | boolean | — | Disables the slider |
invalid | boolean | — | Error state (automatic inside Field) |
name | string | — | For forms: a hidden input per thumb (name[] for a range) |
labels | { thumb?: (index: number, count: number) => string } | 'Qiymat' | 'Minimal qiymat' / 'Maksimal qiymat' | Screen reader names of the thumbs |
aria-label | string | — | Slider name without a Field (combined with the thumb name in a range) |
Data attributes
| Attribute | Where | Value |
|---|---|---|
data-size | root | sm · md |
data-orientation | root, track, thumb | horizontal · vertical |
data-disabled | root, track, thumb | present when disabled |
data-invalid | root | present in error state |
data-slot | track, fill, ... | slider-track · slider-range · slider-thumb · slider-value · slider-marks |
Keyboard
| Key | Action |
|---|---|
← ↓ / → ↑ | Decrease / increase by one step |
PageDown / PageUp | Decrease / increase by 10 steps |
Shift + arrow | 10 steps |
Home / End | min / max |
Tab | Move to the next thumb |
Accessibility
- Role. Each thumb is
role="slider"witharia-valuemin,aria-valuemax,aria-valuenowandaria-orientation. Built on Radix Slider. - Value text.
aria-valuetextis theformatValueresult, so screen readers say "1 500 000 so'm" instead of "1500000". - Name. Inside a
Fieldthe thumb is linked to the label viaaria-labelledby. In a range each thumb is named label + "Minimal qiymat" / "Maksimal qiymat" (change it withlabels.thumb). Without a Field, passaria-label. - Errors. The Field's description and error go to
aria-describedby, the error state toaria-invalid. - The bubble and marks are decorative —
aria-hidden(the value is already inaria-valuetext). - Touch. The thumb has an invisible hit area of at least 40px.