Slider
The Slider component provides users with an input for a one or more numerical values within a given range.
'use client';
import * as React from 'react';
import { useTheme } from '@mui/system';
import { Slider } from '@base_ui/react/Slider';
import classes from '../../styles.module.css';
export default function UnstyledSliderIntroduction() {
  // Replace this with your app logic for determining dark mode
  const isDarkMode = useIsDarkMode();
  return (
    <div
      className={isDarkMode ? 'dark' : ''}
      style={{ display: 'flex', flexDirection: 'column', gap: '4rem', width: 320 }}
    >
      <Slider.Root
        className={classes.slider}
        defaultValue={50}
        aria-labelledby="VolumeSliderLabel"
      >
        <Label
          id="VolumeSliderLabel"
          htmlFor=":slider-thumb-input:"
          className={classes.label}
        >
          Volume
        </Label>
        <Slider.Output className={classes.output} />
        <Slider.Control className={classes.control}>
          <Slider.Track className={classes.track}>
            <Slider.Indicator className={classes.indicator} />
            <Slider.Thumb className={classes.thumb} inputId=":slider-thumb-input:" />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
    </div>
  );
}
function Label(props: React.LabelHTMLAttributes<HTMLLabelElement>) {
  const { id, htmlFor, ...otherProps } = props;
  return <label id={id} htmlFor={htmlFor} {...otherProps} />;
}
function useIsDarkMode() {
  const theme = useTheme();
  return theme.palette.mode === 'dark';
}
Installation
Base UI components are all available as a single package.
npm install @base_ui/react
Once you have the package installed, import the component.
import { Slider } from '@base_ui/react/Slider';
Anatomy
Sliders are implemented using a collection of related components:
- <Slider.Root />is a top-level component that wraps the other components.
- <Slider.Output />renders the value of the slider.
- <Slider.Control />renders the click/touch area that contains the track and thumb.
- <Slider.Track />renders the visible rail on the- Controlalong which the thumb(s) can be moved.
- <Slider.Indicator />renders the filled portion of the track which represents the value(s).
- <Slider.Thumb />renders the element that can be moved along the track to change the value.
<Slider.Root>
  <Slider.Output />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
    <Slider.Track/>
  </Slider.Control>
</Slider.Root>Value
Default value
When Slider is uncontrolled, the defaultValue prop sets the initial value of the component.
function App() {
  return (
    <Slider.Root defaultValue={50}>
      <Slider.Output />
      <Slider.Control>
        <Slider.Track>
          <Slider.Indicator />
          <Slider.Thumb />
        <Slider.Track/>
      </Slider.Control>
    </Slider.Root>
  );
}Controlled
When Slider is uncontrolled, the value prop holds the numerical value(s), and two callbacks are provided for when the value changes:
- onValueChangeis called when the value is changing while the thumb is being moved along the control area
- onValueCommittedis called when thumb stops moving and either- pointerupor- keydownare triggered
function App() {
  const [value, setValue] = useState(50);
  return (
    <Slider.Root value={value} onValueChange={setValue}>
      <Slider.Output />
      <Slider.Control>
        <Slider.Track>
          <Slider.Indicator />
          <Slider.Thumb />
        <Slider.Track/>
      </Slider.Control>
    </Slider.Root>
  );
}Validation
Min and max
The min and max props can be used to restrict the value(s) within a range.
<Slider.Root min={1} max={9}>
  <Slider.Output />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
    <Slider.Track/>
  </Slider.Control>
</Slider.Root>Step
The step prop snaps each value to multiples of the given number. In the below example, the input value snaps to increments of 4 starting from the initial defaultValue: 3, 7, 11, 15, and so on.
<Slider.Root step={4} defaultValue={3}>
  <Slider.Output />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
    <Slider.Track/>
  </Slider.Control>
</Slider.Root>You can specify the largeStep prop to change the step when the user holds the shift key, snapping to multiples of 10 by default.
Range Sliders
To let users set the start and end of a range on a Slider, provide an array of values to the value or defaultValue prop, and place a <Slider.Thumb /> for each value in the array:
<Slider.Root defaultValue={[45, 70]}>
  <Slider.Output />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
      <Slider.Thumb />
    <Slider.Track/>
  </Slider.Control>
</Slider.Root>'use client';
import * as React from 'react';
import { useTheme } from '@mui/system';
import { Slider } from '@base_ui/react/Slider';
import classes from './styles.module.css';
export default function RangeSlider() {
  // Replace this with your app logic for determining dark mode
  const isDarkMode = useIsDarkMode();
  const [value, setValue] = React.useState<number | number[]>([20, 37]);
  return (
    <div
      className={isDarkMode ? 'dark' : ''}
      style={{ display: 'flex', flexDirection: 'column', gap: '4rem', width: 320 }}
    >
      {/* controlled: */}
      <Slider.Root
        value={value}
        onValueChange={setValue}
        aria-labelledby="ControlledRangeLabel"
        className={classes.slider}
      >
        {/*
          we can't use a <label> element in a range slider since the `for` attribute
          cannot reference more than one <input> element
          although the html spec doesn't forbid <label> without `for`:
          https://html.spec.whatwg.org/multipage/forms.html#the-label-element
          eslint complains by default and a11y validators may complain e.g. WAVE
        */}
        <span id="ControlledRangeLabel" className={classes.label}>
          Controlled Range
        </span>
        <Slider.Output className={classes.output} />
        <Slider.Control className={classes.control}>
          <Slider.Track className={classes.track}>
            <Slider.Indicator className={classes.indicator} />
            <Slider.Thumb className={classes.thumb} />
            <Slider.Thumb className={classes.thumb} />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
      {/* uncontrolled: */}
      <Slider.Root
        defaultValue={[20, 37]}
        aria-labelledby="UncontrolledRangeLabel"
        className={classes.slider}
      >
        <span id="UncontrolledRangeLabel" className={classes.label}>
          Uncontrolled Range
        </span>
        <Slider.Output className={classes.output} />
        <Slider.Control className={classes.control}>
          <Slider.Track className={classes.track}>
            <Slider.Indicator className={classes.indicator} />
            <Slider.Thumb className={classes.thumb} />
            <Slider.Thumb className={classes.thumb} />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
    </div>
  );
}
function useIsDarkMode() {
  const theme = useTheme();
  return theme.palette.mode === 'dark';
}
Overlapping values
The minStepsBetweenValues prop can be used to to set the mininum number of steps between values in a range slider, so thumbs do not overlap in the same position:
<Slider.Root minStepsBetweenValues={2} step={5} defaultValue={[10, 20]}>
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
      <Slider.Thumb />
    <Slider.Track/>
  </Slider.Control>
</Slider.Root>Vertical
To create vertical sliders, set the orientation prop to "vertical". This will track thumb movements vertically instead of horizontally.
<Slider.Root orientation="vertical">{/* Subcomponents */}</Slider.Root>'use client';
import * as React from 'react';
import { Slider } from '@base_ui/react/Slider';
import { useTheme } from '@mui/system';
import classes from './vertical.module.css';
export default function VerticalSlider() {
  // Replace this with your app logic for determining dark mode
  const isDarkMode = useIsDarkMode();
  return (
    <div className={isDarkMode ? 'dark' : ''}>
      <Slider.Root
        defaultValue={50}
        orientation="vertical"
        aria-labelledby="VolumeSliderLabel"
        className={classes.slider}
      >
        <Label
          id="VolumeSliderLabel"
          htmlFor=":slider-thumb-input-vertical:"
          className={classes.label}
        >
          Volume
        </Label>
        <Slider.Control className={classes.control}>
          <Slider.Track className={classes.track}>
            <Slider.Indicator className={classes.indicator} />
            <Slider.Thumb
              className={classes.thumb}
              inputId=":slider-thumb-input-vertical:"
            />
          </Slider.Track>
        </Slider.Control>
        <Slider.Output className={classes.output} />
      </Slider.Root>
    </div>
  );
}
function Label(props: React.LabelHTMLAttributes<HTMLLabelElement>) {
  const { id, htmlFor, ...otherProps } = props;
  return <label id={id} htmlFor={htmlFor} {...otherProps} />;
}
function useIsDarkMode() {
  const theme = useTheme();
  return theme.palette.mode === 'dark';
}
Chrome versions below 124 does not implement aria-orientation correctly for vertical sliders (Chromium issue #40736841), and exposes them in the accessibility tree as horizontal.
The -webkit-appearance: slider-vertical CSS property can be used to correct this, though it will trigger a console warning in newer Chrome versions:
.MySlider-thumb > input {
  -webkit-appearance: slider-vertical;
}The Slider.Thumb subcomponent automatically sets the CSS writing-mode property that fixes this bug for Chrome 124 and newer.
RTL
Set the direction prop to 'rtl' to change the slider's direction for right-to-left languages:
<Slider.Root direction="rtl">{/* Subcomponents */}</Slider.Root>In a RTL Slider, Left Arrow increases the value while Right Arrow decreases the value.
'use client';
import * as React from 'react';
import { Slider } from '@base_ui/react/Slider';
import { useTheme } from '@mui/system';
import classes from './styles.module.css';
export default function RtlSlider() {
  // Replace this with your app logic for determining dark mode
  const isDarkMode = useIsDarkMode();
  return (
    <div className={isDarkMode ? 'dark' : ''} style={{ width: 320, margin: 32 }}>
      <Slider.Root
        defaultValue={50}
        aria-labelledby="VolumeSliderLabel"
        direction="rtl"
        className={classes.slider}
      >
        <Label
          id="VolumeSliderLabel"
          htmlFor=":slider-thumb-input-rtl:"
          className={classes.label}
        >
          Volume (RTL)
        </Label>
        <Slider.Output className={classes.output} />
        <Slider.Control className={classes.control}>
          <Slider.Track className={classes.track}>
            <Slider.Indicator className={classes.indicator} />
            <Slider.Thumb
              className={classes.thumb}
              inputId=":slider-thumb-input-rtl:"
            />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
    </div>
  );
}
function Label(props: React.LabelHTMLAttributes<HTMLLabelElement>) {
  const { id, htmlFor, ...otherProps } = props;
  return <label id={id} htmlFor={htmlFor} {...otherProps} />;
}
function useIsDarkMode() {
  const theme = useTheme();
  return theme.palette.mode === 'dark';
}
Overriding default components
Use the render prop to override the rendered elements with your own components.
<Slider.Control render={(props) => <MyCustomTrack {...props} />}> />All subcomponents accept the render prop.
The Slider.Thumb component's render prop contains an additional inputProps argument for rendering an input element attached to the thumb:
<Slider.Thumb
  render={(props, inputProps) => {
    const { children, ...other } = props;
    return (
      <MyCustomThumb {...other}>
        {children}
        <input {...inputProps}>
      <MyCustomThumb/>
    )
  }}>
/>It's handled automatically if you use the shorthand:
<Slider.Thumb render={<MyCustomThumb />} />Accessibility
See the WAI-ARIA guide on the Slider (Multi-Thumb) pattern for complete details on accessibility best practices.
The component handles most of the work necessary to make it accessible. However, you need to make sure that:
- Each thumb has a user-friendly label (aria-label,aria-labelledbyorgetAriaLabelprop).
- Each thumb has a user-friendly text for its current value.
This is not required if the value matches the semantics of the label.
You can change the name with the getAriaValueTextoraria-valuetextprop.
API Reference
SliderRoot
| Prop | Type | Default | Description | 
|---|---|---|---|
| aria-labelledby | string | The id of the element containing a label for the slider. | |
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| defaultValue | union | The default value of the slider. Use when the component is not controlled. | |
| direction | enum | 'ltr' | Sets the direction. For right-to-left languages, the lowest value is on the right-hand side. | 
| disabled | bool | false | If true, the component is disabled. | 
| largeStep | number | 10 | The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down. | 
| minStepsBetweenValues | number | 0 | The minimum steps between values in a range slider. | 
| onValueChange | func | Callback function that is fired when the slider's value changed. | |
| onValueCommitted | func | Callback function that is fired when the pointerupis triggered. | |
| orientation | enum | 'horizontal' | The component orientation. | 
| render | union | A function to customize rendering of the component. | |
| value | union | The value of the slider. For ranged sliders, provide an array with two values. | 
SliderOutput
| Prop | Type | Default | Description | 
|---|---|---|---|
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| render | union | A function to customize rendering of the component. | 
SliderControl
| Prop | Type | Default | Description | 
|---|---|---|---|
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| render | union | A function to customize rendering of the component. | 
SliderTrack
| Prop | Type | Default | Description | 
|---|---|---|---|
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| render | union | A function to customize rendering of the component. | 
SliderThumb
| Prop | Type | Default | Description | 
|---|---|---|---|
| aria-label | string | The label for the input element. | |
| aria-valuetext | string | A string value that provides a user-friendly name for the current value of the slider. | |
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| getAriaLabel | func | Accepts a function which returns a string value that provides a user-friendly name for the input associated with the thumb | |
| getAriaValueText | func | Accepts a function which returns a string value that provides a user-friendly name for the current value of the slider. This is important for screen reader users. | |
| render | union | A function to customize rendering of the component. | 
SliderIndicator
| Prop | Type | Default | Description | 
|---|---|---|---|
| className | union | Class names applied to the element or a function that returns them based on the component's state. | |
| render | union | A function to customize rendering of the component. |