Slider
A track with one thumb for a value, or two for a range.
Vibra draws the thumb as a card-plane disc with the --input boundary that turns the accent on hover, where shadcn's is a white disc ringed in --ring with a halo; it takes the kit's solid focus outline, drawn on the thumb while the hidden range input inside it has the focus — the thumb itself never does — and the filled part of the track is the accent through --primary. It draws a thumb per value, from a number or an array, each told its place so a range renders whole on the server. Name it with aria-labelledby or aria-label and describe it with aria-describedby: all three reach the thumbs, where the focus is, whereas Base UI hands a thumb the labelledby alone. getAriaLabel names a range's thumbs apart, and getAriaValueText or format reads the value in its unit — "$60", "90 days" — rather than as a bare number.
Install
npx shadcn@latest add @vibra/sliderNeeds the @vibra registry in your components.json — set it up once.
Examples
"use client"
import * as React from "react"
import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"
export default function SliderDemo() {
const [threshold, setThreshold] = React.useState(80)
return (
<div className="flex w-full max-w-xs flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<Label id="usage-alert-label">Usage alert at</Label>
<span className="text-sm font-medium tabular-nums" aria-hidden="true">
{threshold}%
</span>
</div>
<Slider
aria-labelledby="usage-alert-label"
// An array, even for one thumb: the slider draws a thumb per entry.
value={[threshold]}
min={50}
max={100}
step={5}
onValueChange={(next) => setThreshold(Array.isArray(next) ? next[0] : next)}
// Read as "80%", not a bare 80: the unit is part of the value.
format={{ style: "unit", unit: "percent" }}
/>
<p className="text-xs text-muted-foreground">We email the workspace owners when a plan passes this share.</p>
</div>
)
}Stepped, with its marks
Retention periods that aren't evenly spaced: the thumb moves through their places and reads "90 days", never "2".
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Slider } from "@/components/ui/slider"
const STOPS = [
{ days: 7, label: "7 days" },
{ days: 30, label: "30 days" },
{ days: 90, label: "90 days" },
{ days: 180, label: "6 months" },
{ days: 365, label: "1 year" },
]
// Five retention periods that are not evenly spaced in days, so the slider
// moves through their positions and says each one's length: the thumb reads
// "90 days", never "2". Every stop is marked and named under the track.
export default function SliderSteps() {
const id = React.useId()
const [stop, setStop] = React.useState(2)
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Keep raw events for
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{STOPS[stop].label}
</span>
</div>
<Slider
aria-labelledby={id}
aria-describedby={`${id}-hint`}
value={[stop]}
min={0}
max={STOPS.length - 1}
step={1}
onValueChange={(next) => setStop(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(_, value) => STOPS[value].label}
/>
<ol aria-hidden="true" className="flex justify-between px-1.5">
{STOPS.map((entry, index) => (
<li key={entry.days} className="flex w-0 flex-col items-center gap-1">
<span className={cn("h-1.5 w-px", index <= stop ? "bg-foreground" : "bg-input")} />
<span className={cn("text-xs whitespace-nowrap tabular-nums", index === stop ? "text-foreground" : "text-muted-foreground")}>
{entry.label}
</span>
</li>
))}
</ol>
<p id={`${id}-hint`} className="text-xs text-muted-foreground">
Older events roll up into daily totals, which are kept for good.
</p>
</div>
)
}Words at the ends
A setting without a unit: the ends are named, and the thumb reads the number with what it leans towards.
"use client"
import * as React from "react"
import { Slider } from "@/components/ui/slider"
/** The words for a creativity setting: what 0.7 means to the person choosing it. */
function describe(value: number) {
if (value <= 0.2) return "precise"
if (value < 0.5) return "leaning precise"
if (value === 0.5) return "balanced"
if (value < 0.8) return "leaning creative"
return "creative"
}
// A setting with no unit anyone thinks in: the ends are named in words, and
// the thumb reads the number with what it leans towards, so "0.7" arrives as
// "0.7, leaning creative".
export default function SliderLabelledEnds() {
const id = React.useId()
const [creativity, setCreativity] = React.useState(0.7)
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<span id={id} className="text-sm font-medium">
How the assistant answers
</span>
<div aria-hidden="true" className="flex justify-between text-xs text-muted-foreground">
<span>Precise</span>
<span>Creative</span>
</div>
<Slider
aria-labelledby={id}
aria-describedby={`${id}-hint`}
value={[creativity]}
min={0}
max={1}
step={0.1}
onValueChange={(next) => setCreativity(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(formatted, value) => `${formatted}, ${describe(value)}`}
/>
<p id={`${id}-hint`} className="text-xs text-muted-foreground">
Lower keeps answers close to your data; higher suggests more.{" "}
<span className="text-foreground tabular-nums">{creativity.toFixed(1)}</span> is {describe(creativity)}.
</p>
</div>
)
}Icons at the ends
A slow and a fast picture either side, hidden; the thumb reads "1.2 times normal speed".
"use client"
import * as React from "react"
import { RabbitIcon, TurtleIcon } from "lucide-react"
import { Slider } from "@/components/ui/slider"
const speed = (value: number) => `${value.toFixed(1)}×`
// The voice agent's speaking rate between a slow and a fast picture. The
// pictures are for the eye and hidden; the thumb reads "1.2 times normal
// speed", which is what the picture only suggests.
export default function SliderIcons() {
const id = React.useId()
const [rate, setRate] = React.useState(1.2)
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Speaking rate
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{speed(rate)}
</span>
</div>
<div className="flex items-center gap-3">
<TurtleIcon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
<Slider
aria-labelledby={id}
value={[rate]}
min={0.5}
max={2}
step={0.1}
onValueChange={(next) => setRate(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(_, value) => (value === 1 ? "normal speed" : `${value.toFixed(1)} times normal speed`)}
/>
<RabbitIcon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
</div>
</div>
)
}A range of times
Two thumbs named for their ends and read as times of day, kept at least two hours apart.
"use client"
import * as React from "react"
import { Slider } from "@/components/ui/slider"
/** Minutes past midnight as a time of day: 390 → "6:30 AM". */
function time(minutes: number) {
const hour = Math.floor(minutes / 60) % 24
const minute = minutes % 60
return `${hour % 12 === 0 ? 12 : hour % 12}:${String(minute).padStart(2, "0")} ${hour < 12 ? "AM" : "PM"}`
}
// When scheduled reports may go out: a range of times in half hours, each
// thumb named for its end and read as a time of day, not a count of minutes.
// The window can't close to less than two hours.
export default function SliderTimeRange() {
const id = React.useId()
const [span, setSpan] = React.useState([7 * 60, 19 * 60])
const [start, end] = span
const hours = (end - start) / 60
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Send reports between
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{time(start)} – {time(end)}
</span>
</div>
<Slider
aria-labelledby={id}
aria-describedby={`${id}-hint`}
value={span}
min={0}
max={24 * 60}
step={30}
largeStep={120}
minStepsBetweenValues={4}
onValueChange={(next) => setSpan(next as number[])}
getAriaLabel={(thumb) => (thumb === 0 ? "Earliest send time" : "Latest send time")}
getAriaValueText={(_, value) => time(value)}
/>
<div aria-hidden="true" className="flex justify-between text-xs text-muted-foreground tabular-nums">
<span>12 AM</span>
<span>6 AM</span>
<span>12 PM</span>
<span>6 PM</span>
<span>12 AM</span>
</div>
<p id={`${id}-hint`} className="text-xs text-muted-foreground">
A {hours}-hour window in the workspace time zone, Europe/Dublin. Page Up and Page Down move two hours.
</p>
</div>
)
}A price range over its orders
The range filter over the distribution it cuts: the bars it keeps are drawn in the ink, and the count under it follows.
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Slider } from "@/components/ui/slider"
const MAX = 500
const STEP = 20
// Orders in each $20 band of order value, $0 to $500: the shape the filter
// cuts through. UI vocabulary for the example, not rows of data.
const BANDS = [14, 38, 71, 96, 118, 124, 109, 93, 80, 66, 58, 47, 41, 33, 29, 24, 19, 16, 13, 10, 9, 7, 5, 4, 3]
const ORDERS = BANDS.reduce((total, count) => total + count, 0)
const TALLEST = Math.max(...BANDS)
const usd = (value: number) => `$${value.toLocaleString("en-US")}`
// A price-range filter over the distribution it filters: two thumbs, each
// named for its end and read in dollars, and the bars inside the range drawn
// in the ink so the reader sees how much of the shop the range keeps.
export default function SliderHistogram() {
const id = React.useId()
const [range, setRange] = React.useState([60, 300])
const [low, high] = range
const kept = BANDS.reduce((total, count, band) => (band * STEP >= low && band * STEP < high ? total + count : total), 0)
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Order value
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{usd(low)} – {usd(high)}
</span>
</div>
<div aria-hidden="true" className="flex h-12 items-end gap-px px-1.5">
{BANDS.map((count, band) => (
<div
key={band}
className={cn(
"flex-1 rounded-t-xs",
band * STEP >= low && band * STEP < high ? "bg-muted-foreground" : "bg-muted"
)}
style={{ height: `${(count / TALLEST) * 100}%` }}
/>
))}
</div>
<Slider
aria-labelledby={id}
value={range}
min={0}
max={MAX}
step={STEP}
minStepsBetweenValues={1}
onValueChange={(next) => setRange(next as number[])}
getAriaLabel={(thumb) => (thumb === 0 ? "Lowest order value" : "Highest order value")}
getAriaValueText={(_, value) => usd(value)}
/>
<p className="text-xs text-muted-foreground">
<span className="tabular-nums">{kept.toLocaleString("en-US")}</span> of{" "}
<span className="tabular-nums">{ORDERS.toLocaleString("en-US")}</span> orders in the last 30 days.
</p>
</div>
)
}With − and + buttons
A step at a time either side; at the ends the buttons stay focusable and point at the line that says why they stop.
"use client"
import * as React from "react"
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Slider } from "@/components/ui/slider"
const MIN = 1
const MAX = 25
// A seat count with a button either side for a step at a time: each named for
// what it does, and at the end of the range kept focusable and pointed at the
// line that says why it stops there.
export default function SliderButtons() {
const id = React.useId()
const [seats, setSeats] = React.useState(12)
const change = (by: number) => setSeats((current) => Math.min(MAX, Math.max(MIN, current + by)))
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Seats on the Team plan
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{seats} of {MAX}
</span>
</div>
<div className="flex items-center gap-3">
<Button
variant="outline"
size="icon-sm"
aria-label="Remove a seat"
disabled={seats === MIN}
focusableWhenDisabled
aria-describedby={seats === MIN ? `${id}-limit` : undefined}
onClick={() => change(-1)}
>
<MinusIcon aria-hidden="true" />
</Button>
<Slider
aria-labelledby={id}
value={[seats]}
min={MIN}
max={MAX}
onValueChange={(next) => setSeats(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(_, value) => `${value} ${value === 1 ? "seat" : "seats"}`}
/>
<Button
variant="outline"
size="icon-sm"
aria-label="Add a seat"
disabled={seats === MAX}
focusableWhenDisabled
aria-describedby={seats === MAX ? `${id}-limit` : undefined}
onClick={() => change(1)}
>
<PlusIcon aria-hidden="true" />
</Button>
</div>
<p id={`${id}-limit`} className="text-xs text-muted-foreground">
{seats === MAX
? "25 is as many as the Team plan holds. Enterprise has no limit."
: seats === MIN
? "A workspace keeps at least one seat, its owner's."
: `${MAX - seats} more before the plan is full.`}
</p>
</div>
)
}Vertical
An equaliser of three upright sliders, each named by the label under it and read in decibels with its sign.
"use client"
import * as React from "react"
import { Slider } from "@/components/ui/slider"
const BANDS = [
{ key: "low", label: "Low", detail: "Below 250 Hz" },
{ key: "mid", label: "Mid", detail: "250 Hz to 4 kHz" },
{ key: "high", label: "High", detail: "Above 4 kHz" },
]
const decibels = (value: number) => `${value > 0 ? "+" : value < 0 ? "−" : ""}${Math.abs(value)} dB`
// The voice agent's equaliser: three upright sliders, up for more. Each is
// named by the label under it and described by its range of pitch, and reads
// its level in decibels with its sign.
export default function SliderVertical() {
const id = React.useId()
const [levels, setLevels] = React.useState<Record<string, number>>({ low: -3, mid: 2, high: 4 })
return (
<div role="group" aria-labelledby={`${id}-title`} className="flex flex-col items-center gap-3">
<span id={`${id}-title`} className="text-sm font-medium">
Voice equaliser
</span>
<div className="flex gap-8">
{BANDS.map((band) => (
<div key={band.key} className="flex h-52 flex-col items-center gap-2">
<span className="text-xs text-muted-foreground tabular-nums" aria-hidden="true">
{decibels(levels[band.key])}
</span>
<Slider
orientation="vertical"
aria-labelledby={`${id}-${band.key}`}
aria-describedby={`${id}-${band.key}-detail`}
value={[levels[band.key]]}
min={-12}
max={12}
step={1}
onValueChange={(next) =>
setLevels((current) => ({ ...current, [band.key]: Array.isArray(next) ? next[0] : next }))
}
getAriaValueText={(_, value) => (value === 0 ? "0 decibels" : `${value > 0 ? "plus" : "minus"} ${Math.abs(value)} decibels`)}
/>
<span id={`${id}-${band.key}`} className="text-xs font-medium">
{band.label}
</span>
<span id={`${id}-${band.key}-detail`} className="sr-only">
{band.detail}
</span>
</div>
))}
</div>
</div>
)
}A threshold marked
A mark on the track where the rule changes, and past it a line that says who signs off, as the thumb's description.
"use client"
import * as React from "react"
import { Slider } from "@/components/ui/slider"
const MAX = 40
const APPROVAL = 20
// A discount a rep can give on their own up to 20%, marked on the track where
// the rule changes. Past it the line under the slider says who has to sign
// off, and the thumb carries that line as its description.
export default function SliderThreshold() {
const id = React.useId()
const [discount, setDiscount] = React.useState(15)
const needsApproval = discount > APPROVAL
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Discount on the Keystone renewal
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{discount}%
</span>
</div>
<div className="relative">
<Slider
aria-labelledby={id}
aria-describedby={`${id}-rule`}
value={[discount]}
min={0}
max={MAX}
onValueChange={(next) => setDiscount(Array.isArray(next) ? next[0] : next)}
format={{ style: "unit", unit: "percent" }}
/>
<span
aria-hidden="true"
className="pointer-events-none absolute top-1/2 h-3 w-px -translate-y-1/2 bg-foreground"
// Where 20% falls: a thumb's half-width in from each end, as the thumbs travel.
style={{ insetInlineStart: `calc(6px + (100% - 12px) * ${APPROVAL / MAX})` }}
/>
</div>
<p id={`${id}-rule`} aria-live="polite" className="text-xs text-muted-foreground">
{needsApproval
? `Over ${APPROVAL}% needs Petra Lindqvist to approve before the quote goes out.`
: `Up to ${APPROVAL}% is yours to give without approval.`}
</p>
</div>
)
}Applies on release
The readout follows the drag while the query waits for onValueCommitted, and a status line says when it has run.
"use client"
import * as React from "react"
import { Slider } from "@/components/ui/slider"
const SESSIONS = 1_240_000
// Re-running the funnel on every step of a drag would query the warehouse
// dozens of times, so the readout follows the thumb while the query waits for
// the release (onValueCommitted) — and the status line says when it has run.
export default function SliderCommit() {
const id = React.useId()
const [sample, setSample] = React.useState(25)
const [applied, setApplied] = React.useState(25)
const sessions = Math.round((SESSIONS * applied) / 100)
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id={id} className="text-sm font-medium">
Funnel sample
</span>
<span className="text-sm tabular-nums" aria-hidden="true">
{sample}% of sessions
</span>
</div>
<Slider
aria-labelledby={id}
value={[sample]}
min={5}
max={100}
step={5}
onValueChange={(next) => setSample(Array.isArray(next) ? next[0] : next)}
onValueCommitted={(next) => setApplied(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(_, value) => `${value}% of sessions`}
/>
<p role="status" aria-live="polite" className="text-xs text-muted-foreground">
Showing a {applied}% sample: <span className="tabular-nums">{sessions.toLocaleString("en-US")}</span> of{" "}
<span className="tabular-nums">{SESSIONS.toLocaleString("en-US")}</span> sessions.
</p>
</div>
)
}Disabled, with the reason
A limit set by someone else still shows where it stands, and the line under it says who set it and where it changes.
import { Slider } from "@/components/ui/slider"
// A limit set by someone else: the slider still shows where it stands,
// dimmed and out of the tab order, and the line under it says who set it and
// where it can change.
export default function SliderDisabled() {
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-baseline justify-between gap-3">
<span id="slider-disabled-label" className="text-sm font-medium">
Monthly AI spend cap
</span>
<span className="text-sm text-muted-foreground tabular-nums" aria-hidden="true">
$2,000
</span>
</div>
<Slider
aria-labelledby="slider-disabled-label"
aria-describedby="slider-disabled-why"
defaultValue={[2000]}
min={0}
max={5000}
step={100}
disabled
format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
/>
<p id="slider-disabled-why" className="text-xs text-muted-foreground">
Set by Saoirse Byrne for the whole workspace. Workspace owners change it under Billing.
</p>
</div>
)
}Right to left
The demo's alert in Arabic, in a DirectionProvider: the fill grows from the right and the left arrow raises the value.
"use client"
import * as React from "react"
import { DirectionProvider } from "@/components/ui/direction"
import { Slider } from "@/components/ui/slider"
// The demo's usage alert in Arabic. The fill grows from the right and the
// arrow keys follow the reading direction — the left arrow raises the value —
// which Base UI takes from the direction provider rather than the page.
export default function SliderRtl() {
const id = React.useId()
const [threshold, setThreshold] = React.useState(80)
return (
<DirectionProvider direction="rtl">
<div dir="rtl" lang="ar" className="flex w-full max-w-xs flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<span id={id} className="text-sm font-medium">
التنبيه عند استخدام
</span>
<span className="text-sm font-medium tabular-nums" aria-hidden="true">
{threshold}٪
</span>
</div>
<Slider
aria-labelledby={id}
value={[threshold]}
min={50}
max={100}
step={5}
onValueChange={(next) => setThreshold(Array.isArray(next) ? next[0] : next)}
getAriaValueText={(_, value) => `${value}٪ من الخطة`}
/>
<p className="text-xs text-muted-foreground">نرسل بريدًا إلى مالكي مساحة العمل عندما يتجاوز الاستخدام هذه النسبة.</p>
</div>
</DirectionProvider>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | number[] | — | One entry per thumb: [80] draws one, [60, 300] a range. |
| min / max / step | number | 0 / 100 / 1 | The scale and its steps; a scale that isn't evenly spaced moves through indexes and names each in getAriaValueText. |
| largeStep | number | 10 | How far Page Up, Page Down and Shift with an arrow move, in the value's own units. |
| minStepsBetweenValues | number | 0 | The least gap between a range's thumbs, counted in steps. |
| orientation | "horizontal" | "vertical" | "horizontal" | Upright for a mixer or a level; give the slider's box a height. |
| getAriaLabel | (index: number) => string | — | A name per thumb — "Lowest order value", "Highest order value" — so a range's two ends are told apart. |
| getAriaValueText | (formatted: string, value: number, index: number) => string | — | What a screen reader hears for a value, in its unit: "$60", "90 days", "6:30 AM". |
| format | Intl.NumberFormatOptions | — | Formats the value for the thumb when getAriaValueText is not given, e.g. a currency or a percent unit. |
| onValueCommitted | (value) => void | — | Fires once, on release or on a key, where onValueChange fires on every step of a drag. |
| disabled | boolean | false | Dims the slider and takes its thumbs out of the tab order, so write why beside it. |
Dependencies
Registry
Source
import { Slider as SliderPrimitive } from "@base-ui/react/slider"
import { cn } from "@/lib/utils"
type SliderProps = SliderPrimitive.Root.Props &
Pick<SliderPrimitive.Thumb.Props, "getAriaLabel" | "getAriaValueText">
function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
getAriaLabel,
getAriaValueText,
"aria-label": ariaLabel,
"aria-describedby": ariaDescribedBy,
...props
}: SliderProps) {
// One thumb per value. A plain number is one value too: read as neither
// array, it fell through to [min, max] and drew two thumbs on the same spot.
const _values = Array.isArray(value)
? value
: typeof value === "number"
? [value]
: Array.isArray(defaultValue)
? defaultValue
: typeof defaultValue === "number"
? [defaultValue]
: [min, max]
return (
<SliderPrimitive.Root
className={cn("data-horizontal:w-full data-vertical:h-full", className)}
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
thumbAlignment="edge"
{...props}
>
<SliderPrimitive.Control className="relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:min-h-40 data-vertical:w-auto data-vertical:flex-col">
<SliderPrimitive.Track
data-slot="slider-track"
className="relative grow overflow-hidden rounded-full bg-muted select-none data-horizontal:h-1 data-horizontal:w-full data-vertical:h-full data-vertical:w-1"
>
<SliderPrimitive.Indicator
data-slot="slider-range"
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
// Its place among the thumbs, known before mount, so a range
// renders each thumb with its value on the server.
index={index}
// Base UI hands a thumb the root's aria-labelledby and nothing
// else. The name, the description and the words for the value
// belong on the thumb, where the focus is.
aria-label={ariaLabel}
aria-describedby={ariaDescribedBy}
getAriaLabel={getAriaLabel}
getAriaValueText={getAriaValueText}
// The focus lands on the visually hidden range input inside the
// thumb, never on the thumb itself, so the thumb draws the kit's
// outline for it: 2px in the accent, 2px off the disc.
className="relative block size-3 shrink-0 rounded-full border border-input bg-card shadow-(--shadow-sheet) transition-[border-color,box-shadow] duration-(--duration-fast) ease-(--ease-standard) outline-none select-none after:absolute after:-inset-2 hover:border-brand has-[:focus-visible]:focus-outline disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Control>
</SliderPrimitive.Root>
)
}
export { Slider }