Qure UI
Components

Slider

A value, or a range of values, chosen by dragging.

'use client'import { Slider, SliderControl, SliderIndicator, SliderThumb, SliderTrack } from '@/registry/qure/ui/slider'export default function SliderDemo() {  return (    <Slider defaultValue={65} className="max-w-sm">      <SliderControl>        <SliderTrack>          <SliderIndicator />          {/* The label belongs on the thumb, not the root — the thumb is what              carries the hidden range input a screen reader reads. */}          <SliderThumb aria-label="Overlay opacity" />        </SliderTrack>      </SliderControl>    </Slider>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/slider.json

Usage

import {
  Slider, SliderControl, SliderIndicator, SliderLabel, SliderThumb, SliderTrack, SliderValue,
} from '@/components/ui/slider'
<Slider defaultValue={65}>
  <SliderControl>
    <SliderTrack>
      <SliderIndicator />
      <SliderThumb aria-label="Overlay opacity" />
    </SliderTrack>
  </SliderControl>
</Slider>

A slider is for a value whose exact number does not matter much — opacity, brightness, a confidence threshold being tuned by eye. When the number does matter, and especially when it will be typed or audited, use a Number Field: a slider cannot be dictated, cannot be pasted, and rounds whatever the user meant to the nearest step.

Composition

<Slider>              {/* owns the value, min/max/step, orientation */}
  <SliderLabel>       {/* names the control */}
  <SliderValue>       {/* an <output>, announced as it changes */}
  <SliderControl>     {/* the pointer target — full field height */}
    <SliderTrack>     {/* the thin bar */}
      <SliderIndicator />  {/* the filled part */}
      <SliderThumb />      {/* one per value; positions itself in the track */}
    </SliderTrack>
  </SliderControl>
</Slider>

The thumb is a child of the track and positions itself absolutely against it, so the track must not be overflow-hidden — clipping the overflow clips the thumb. The control is deliberately taller than the track: a 6px pointer target would fail WCAG 2.5.8, and a thick track to fix that would be a worse-looking slider.

aria-label goes on SliderThumb, not on Slider. The thumb owns the hidden range input a screen reader actually reads; a label on the root wrapper is announced on a group and leaves the input unnamed.

With a readout

AI heatmap opacity
40%
'use client'import {  Slider,  SliderControl,  SliderIndicator,  SliderLabel,  SliderThumb,  SliderTrack,  SliderValue,} from '@/registry/qure/ui/slider'export default function SliderValueDemo() {  return (    <Slider defaultValue={40} min={0} max={100} step={5} className="max-w-sm">      <div className="flex items-baseline justify-between">        <SliderLabel>AI heatmap opacity</SliderLabel>        <SliderValue>{(formatted) => `${formatted[0]}%`}</SliderValue>      </div>      <SliderControl>        <SliderTrack>          <SliderIndicator />          <SliderThumb />        </SliderTrack>      </SliderControl>    </Slider>  )}

SliderValue takes a render function receiving the formatted strings and the raw numbers, so the unit lives with the value rather than in the label. It renders an <output>, which is announced as it changes without a live region of your own.

Range

Window (HU)
-1,000 to 400
'use client'import {  Slider,  SliderControl,  SliderIndicator,  SliderLabel,  SliderThumb,  SliderTrack,  SliderValue,} from '@/registry/qure/ui/slider'export default function SliderRange() {  return (    <Slider      defaultValue={[-1000, 400]}      min={-1024}      max={3071}      step={10}      minStepsBetweenValues={10}      className="max-w-sm"    >      <div className="flex items-baseline justify-between">        <SliderLabel>Window (HU)</SliderLabel>        <SliderValue>{(formatted) => `${formatted[0]} to ${formatted[1]}`}</SliderValue>      </div>      <SliderControl>        <SliderTrack>          <SliderIndicator />          <SliderThumb getAriaLabel={(index) => (index === 0 ? 'Window minimum' : 'Window maximum')} />          <SliderThumb getAriaLabel={(index) => (index === 0 ? 'Window minimum' : 'Window maximum')} />        </SliderTrack>      </SliderControl>    </Slider>  )}

Pass an array and you get one thumb per entry. This is the natural shape for a CT window — a lower and an upper Hounsfield bound that only mean anything together. minStepsBetweenValues stops the two thumbs collapsing onto each other, and thumbCollisionBehavior decides whether they push, swap or block when they meet.

Size

'use client'import { Slider, SliderControl, SliderIndicator, SliderThumb, SliderTrack } from '@/registry/qure/ui/slider'export default function SliderSize() {  return (    <div className="flex w-full max-w-sm flex-col gap-2">      {(['sm', 'md', 'lg'] as const).map((size) => (        <Slider key={size} size={size} defaultValue={50}>          <SliderControl>            <SliderTrack>              <SliderIndicator />              <SliderThumb aria-label={`Brightness (${size})`} />            </SliderTrack>          </SliderControl>        </Slider>      ))}    </div>  )}

The control row is the field height — 32, 40 or 48 — so a slider lines up with an Input beside it. The track and thumb scale with it.

Vertical

'use client'import { Slider, SliderControl, SliderIndicator, SliderThumb, SliderTrack } from '@/registry/qure/ui/slider'export default function SliderVertical() {  return (    <div className="flex h-56 items-stretch gap-8">      <Slider orientation="vertical" defaultValue={60}>        <SliderControl>          <SliderTrack>            <SliderIndicator />            <SliderThumb aria-label="Window width" />          </SliderTrack>        </SliderControl>      </Slider>      <Slider orientation="vertical" defaultValue={35}>        <SliderControl>          <SliderTrack>            <SliderIndicator />            <SliderThumb aria-label="Window level" />          </SliderTrack>        </SliderControl>      </Slider>    </div>  )}

orientation="vertical" for controls that map to something vertical on screen, or for a pair of window controls beside a viewport. The parent has to give it a height; the slider fills it.

Keyboard

Arrow keys step, shift and Page Up/Page Down move by largeStep (10 by default), Home and End jump to the bounds. On a range slider each thumb is separately focusable.

API Reference

Everything Base UI's Slider accepts, plus:

Prop

Type

The ones you will reach for on the root:

Prop

Type

On this page