RoadieRoadie
  • Home
  • Foundations
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeFoundationsComponents
Appearance

Components

  • Navigation

    • Breadcrumb
    • Navigator
    • Steps
    • Tabs
  • Layout

    • Pane
    • ScrollArea
    • Separator
  • Actions

    • Button
    • IconButton
    • Toggle
    • Toggle group
  • Forms

    • Forms overview
    • Autocomplete
    • Checkbox
    • Combobox
    • Field
    • Fieldset
    • Input
    • Label
    • Number field
    • OTP field
    • Radio group
    • Select
    • Slider
    • Switch
    • Textarea
  • Overlays

    • Dialog
    • Drawer
    • Menu
    • Popover
    • Toast
    • Tooltip
  • Collections

    • Accordion
    • Card
    • Carousel
    • Collapsible
    • DataTable
    • List
    • Marquee
    • Table
  • Status

    • Badge
    • Callout
    • Countdown
    • Empty state
    • Meter
    • Progress
    • Skeleton
    • Sparkline
    • StatTile
  • Media & brand

    • Avatar
    • Icon Tile
    • Image
    • Logo
    • QR code
    • SpotIllustration
  • Text

    • CalendarTile
    • Code
    • DateTime
    • Duration
    • Highlight
    • Mark
    • Prose

Slider

A track with a thumb to drag for a number or a range.

Import

import { Slider } from '@oztix/roadie-components/slider'

Examples

Default

Pass a label and a defaultValue. The value shows beside the label.

<Slider label='Volume' defaultValue={40} />

Range

Pass an array for a range. Each value gets its own thumb. format takes Intl.NumberFormat options, so a price reads in dollars. Values format in en-AU unless you pass a locale.

<Slider
  label='Price'
  defaultValue={[40, 150]}
  max={250}
  step={5}
  format={{ style: 'currency', currency: 'AUD', maximumFractionDigits: 0 }}
/>

Units

Use a unit format for distances, durations and sizes.

<Slider
  label='Search radius'
  defaultValue={25}
  min={5}
  max={100}
  step={5}
  format={{ style: 'unit', unit: 'kilometer' }}
/>

Steps and marks

step snaps the value, and largeStep sets the jump for Page Up, Page Down and Shift with an arrow key. For tick marks, compose the parts and place each mark along the track as a percentage.

const marks = [0, 25, 50, 75, 100]

render(
  <Slider defaultValue={50} step={25} largeStep={50}>
    <Slider.Label>Refund on cancellation</Slider.Label>
    <Slider.Value>{(formatted) => `${formatted[0]}%`}</Slider.Value>
    <Slider.Control>
      <Slider.Track>
        <Slider.Indicator />
        {marks.map((mark) => (
          <span
            key={mark}
            aria-hidden
            className='absolute top-1/2 size-1 -translate-1/2 rounded-full bg-normal'
            style={{ left: `${mark}%` }}
          />
        ))}
        <Slider.Thumb />
      </Slider.Track>
    </Slider.Control>
    <div aria-hidden className='relative col-span-full mx-2.5 h-4 text-xs text-subtle'>
      {marks.map((mark) => (
        <span
          key={mark}
          className='absolute -translate-x-1/2'
          style={{ left: `${mark}%` }}
        >
          {mark}%
        </span>
      ))}
    </div>
  </Slider>
)

Without a label

Leave out label for a bare track, such as a volume control in a player. Give it an aria-label instead.

<Slider aria-label='Volume' defaultValue={60} />

Vertical

Set direction='vertical' for a slider that runs up the page.

<Slider aria-label='Stage monitor level' direction='vertical' defaultValue={70} />

Sizes

size scales the thumb and track. Every size keeps a 44px touch target.

<div className='grid gap-4'>
  <Slider label='Small' size='sm' defaultValue={30} />
  <Slider label='Medium' defaultValue={50} />
  <Slider label='Large' size='lg' defaultValue={70} />
</div>

States

<div className='grid gap-6'>
  <div className='grid gap-1'>
    <p className='text-sm text-subtle'>Default</p>
    <Slider aria-label='Default' defaultValue={40} />
  </div>
  <div className='grid gap-1'>
    <p className='text-sm text-subtle'>Invalid</p>
    <Slider aria-label='Invalid' defaultValue={40} invalid />
  </div>
  <div className='grid gap-1'>
    <p className='text-sm text-subtle'>Disabled</p>
    <Slider aria-label='Disabled' defaultValue={[20, 60]} disabled />
  </div>
</div>

Composition

Compose the parts to name each thumb, such as the lowest and highest price in a range.

<Slider
  defaultValue={[60, 180]}
  max={250}
  format={{ style: 'currency', currency: 'AUD', maximumFractionDigits: 0 }}
>
  <Slider.Label>Ticket price</Slider.Label>
  <Slider.Value />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb index={0} aria-label='Lowest price' />
      <Slider.Thumb index={1} aria-label='Highest price' />
    </Slider.Track>
  </Slider.Control>
</Slider>

With Field

Inside Field, pass label to the slider. It stands in for Field.Label, so the label and value share a line above the track. The slider takes its description from the helper or error text, and invalid and disabled from the field. Clicking the label focuses the first thumb.

<div className='grid gap-6'>
  <Field>
    <Slider
      label='Search radius'
      defaultValue={25}
      min={5}
      max={100}
      step={5}
      format={{ style: 'unit', unit: 'kilometer' }}
    />
    <Field.HelperText>Shows events within this distance of Fitzroy</Field.HelperText>
  </Field>
  <Field invalid>
    <Slider
      label='Price'
      defaultValue={[40, 240]}
      max={250}
      step={5}
      format={{ style: 'currency', currency: 'AUD', maximumFractionDigits: 0 }}
    />
    <Field.ErrorText>Set a top price under $200 to see results</Field.ErrorText>
  </Field>
</div>

Filtering results

onValueChange fires on every step while someone drags. Use onValueCommitted to run a search once they let go.

function PriceFilter() {
  const [range, setRange] = useState([40, 150])

  return (
    <div className='grid gap-3'>
      <Slider
        label='Price'
        defaultValue={range}
        max={250}
        step={5}
        format={{ style: 'currency', currency: 'AUD', maximumFractionDigits: 0 }}
        onValueCommitted={setRange}
      />
      <p className='text-sm text-subtle'>
        Showing events from ${range[0]} to ${range[1]}
      </p>
    </div>
  )
}

render(<PriceFilter />)

Guidelines

Slider for a rough value, a field for an exact one

<Slider label='Price' defaultValue={[40, 150]} max={250} />

Do

Use a slider when close enough is fine and people want to see the range, such as a price filter or a search radius.

<Slider label='Ticket quantity' max={10} />

Don’t

Don't use a slider for an exact number people already know, such as a ticket quantity. Use an input or a select.

Slider to set a value, Meter to show one

Do

Use a slider when people choose the value.

Don’t

Don't use a disabled slider to show a level people can't change, such as capacity sold. Use a Meter.

Show the value

Do

Keep the value visible beside the label, formatted the way people read it, such as "$40 – $150" or "25 km".

Don’t

Don't make people drag to find out where the thumb is. A bare track needs its value shown somewhere nearby.

Accessibility

  • Each thumb holds a native input type='range' with role='slider', so it submits with a form and works with assistive tech.
  • label, Slider.Label or Field.Label names the thumbs. For a range, pass aria-label on each Slider.Thumb to tell them apart.
  • format flows into aria-valuetext, so screen readers hear "$40" rather than "40". A range adds "start range" or "end range".
  • Keyboard: Tab moves between thumbs. The arrow keys move by step. Page Up, Page Down and Shift with an arrow key move by largeStep. Home and End jump to the ends.
  • The thumb looks 16 to 24px across, by size, but takes presses 44px across, and the whole track takes a press to jump there.
  • A focus ring shows on the thumb when it has keyboard focus.
  • In forced colours mode the fill draws in the system text colour and the track and thumb get outlines.

API reference

Slider

Base UI
classNamestring | ((state: SliderRootState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

defaultValueSliderValueType

The uncontrolled value of the slider when it's initially rendered. To render a controlled slider, use the `value` prop instead.

formstring

Identifies the form that owns the slider inputs. Useful when the slider is rendered outside the form.

stepnumber

The granularity with which the slider can step through values. (A "discrete" slider.) The `min` prop serves as the origin for the valid values. We recommend (max - min) to be evenly divisible by the step.

Defaults to 1.

disabledboolean

Whether the slider should ignore user interaction.

Defaults to false.

formatNumberFormatOptions

Options to format the value.

localeLocalesArgument

The locale used by `Intl.NumberFormat` when formatting the value. Defaults to the user's runtime locale.

Defaults to en-AU.

maxnumber

The maximum allowed value of the slider. Should not be equal to min.

Defaults to 100.

minnumber

The minimum allowed value of the slider. Should not be equal to max.

Defaults to 0.

minStepsBetweenValuesnumber

The minimum steps between values in a range slider.

Defaults to 0.

namestring

Identifies the field when a form is submitted.

largeStepnumber

The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.

Defaults to 10.

thumbAlignment"center" | "edge" | "edge-client-only"

How the thumb(s) are aligned relative to `Slider.Control` when the value is at `min` or `max`: - `center`: The center of the thumb is aligned with the control edge - `edge`: The thumb is inset within the control such that its edge is aligned with the control edge - `edge-client-only`: Same as `edge` but renders after React hydration on the client, reducing bundle size in return

Defaults to 'center'.

thumbCollisionBehavior"none" | "push" | "swap"

Controls how thumbs behave when they collide during pointer interactions. - `'push'` (default): Thumbs push each other without restoring their previous positions when dragged back. - `'swap'`: Thumbs swap places when dragged past each other. - `'none'`: Thumbs cannot move past each other; excess movement is ignored.

Defaults to 'push'.

valueSliderValueType

The value of the slider. For range sliders, provide an array with one value per thumb.

onValueChange((value: Value extends number ? number : Value, eventDetails: SliderRootChangeEventDetails) => void)

Callback function that is fired when the slider's value changed. Receives the new value as the first argument; the originating event is available as `eventDetails.event`. The value is also reflected on `eventDetails.event.target.value` for form integration. The `eventDetails.reason` indicates what triggered the change: - `'input-change'` when the hidden range input emits a change event (for example, via form integration) - `'track-press'` when the control track is pressed - `'drag'` while dragging a thumb - `'keyboard'` for keyboard input - `'none'` when the change is triggered without a specific interaction

onValueCommitted((value: Value extends number ? number : Value, eventDetails: SliderRootCommitEventDetails) => void)

Callback function that is fired when a value change is committed. Does not fire if the value did not change, or if the change was canceled. **Warning**: This is a generic event, not a change event. The `eventDetails.reason` indicates what triggered the commit: - `'drag'` while dragging a thumb - `'track-press'` when the control track is pressed - `'keyboard'` for keyboard input - `'input-change'` when the hidden range input emits a change event (for example, via form integration) - `'none'` when the commit occurs without a specific interaction

direction"horizontal" | "vertical"

Which way the track runs. `vertical` runs up the page.

Defaults to 'horizontal'.

size"sm" | "md" | "lg"

Defaults to 'md'.

labelReactNode

A visible label, with the value shown beside it. Inside `Field` it stands in for `Field.Label`.

invalidboolean

Marks the slider invalid. Inherits from `Field` when unset.

Slider.Control

Base UI

No additional props. It forwards all standard HTML attributes to the underlying element.

Slider.Indicator

Base UI

No additional props. It forwards all standard HTML attributes to the underlying element.

Slider.Label

Base UI

No additional props. It forwards all standard HTML attributes to the underlying element.

Slider.Thumb

Base UI
classNamestring | ((state: SliderThumbState) => string)

CSS class applied to the element, or a function that returns a class based on the component's state.

Inherited from SliderThumbProps

disabledboolean

Whether the thumb should ignore user interaction.

Defaults to false.

aria-valuetextstring

A string value forwarded to the [`aria-valuetext`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the `input`. Ignored when `getAriaValueText` is provided.

getAriaLabel((index: number) => string) | null

A function which returns a string value for the [`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label) attribute of the `input`.

getAriaValueText((formattedValue: string, value: number, index: number) => string) | null

A function which returns a string value for the [`aria-valuetext`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the `input`. This is important for screen reader users.

indexnumber

The index of the thumb which corresponds to the index of its value in the `value` or `defaultValue` array. This prop is required to support server-side rendering for range sliders with multiple thumbs. @example ```tsx <Slider.Root value={[10, 20]}> <Slider.Thumb index={0} /> <Slider.Thumb index={1} /> </Slider.Root> ```

inputRefRef<HTMLInputElement>

A ref to access the nested input element.

onBlurFocusEventHandler<HTMLInputElement>

A blur handler forwarded to the `input`.

onFocusFocusEventHandler<HTMLInputElement>

A focus handler forwarded to the `input`.

onKeyDownKeyboardEventHandler<HTMLInputElement>

A keydown handler forwarded to the `input`.

tabIndexnumber

Optional tab index attribute forwarded to the `input`.

Slider.Track

Base UI

No additional props. It forwards all standard HTML attributes to the underlying element.

Slider.Value

Base UI

No additional props. It forwards all standard HTML attributes to the underlying element.

Previous page← SelectNext pageSwitch →