Slider

A slider allows a user to select one or more values within a range.

installyarn add react-aria-components
version3.17.0
usageimport {Slider} from 'react-aria-components'

Example#


import {Label, Slider, SliderOutput, SliderThumb, SliderTrack} from 'react-aria-components';

<Slider defaultValue={30}>
  <Label>Opacity</Label>
  <SliderOutput />
  <SliderTrack>
    <SliderThumb />
  </SliderTrack>
</Slider>
import {
  Label,
  Slider,
  SliderOutput,
  SliderThumb,
  SliderTrack
} from 'react-aria-components';

<Slider defaultValue={30}>
  <Label>Opacity</Label>
  <SliderOutput />
  <SliderTrack>
    <SliderThumb />
  </SliderTrack>
</Slider>
import {
  Label,
  Slider,
  SliderOutput,
  SliderThumb,
  SliderTrack
} from 'react-aria-components';

<Slider
  defaultValue={30}
>
  <Label>
    Opacity
  </Label>
  <SliderOutput />
  <SliderTrack>
    <SliderThumb />
  </SliderTrack>
</Slider>
Show CSS
.react-aria-Slider {
  display: grid;
  grid-template-areas: "label output"
                       "track track";
  grid-template-columns: 1fr auto;
  max-width: 300px;

  & label {
    grid-area: label;
  }

  & output {
    grid-area: output;
  }

  & .react-aria-SliderTrack {
    grid-area: track;
    position: relative;

    /* track line */
    &:before {
      content: '';
      display: block;
      position: absolute;
      background: gray;
    }
  }

  & .react-aria-SliderThumb {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: slateblue;
    border: 2px solid var(--spectrum-alias-background-color-default);

    &[data-dragging] {
      background: #4837b5;
    }

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue;
    }
  }

  &[data-orientation=horizontal] {
    flex-direction: column;
    width: 300px;

    & .react-aria-SliderTrack {
      height: 30px;
      width: 100%;

      &:before {
        height: 3px;
        width: 100%;
        top: 50%;
        transform: translateY(-50%);
      }
    }

    & .react-aria-SliderThumb {
      top: 50%;
    }
  }

  &[data-orientation=vertical] {
    height: 150px;
    display: block;

    & label,
    & output {
      display: none;
    }

    & .react-aria-SliderTrack {
      width: 30px;
      height: 100%;

      &:before {
        width: 3px;
        height: 100%;
        left: 50%;
        transform: translateX(-50%);
      }
    }

    & .react-aria-SliderThumb {
      left: 50%;
    }
  }

  &[data-disabled] {
    & .react-aria-SliderTrack {
      opacity: 0.4;
    }
  }
}
.react-aria-Slider {
  display: grid;
  grid-template-areas: "label output"
                       "track track";
  grid-template-columns: 1fr auto;
  max-width: 300px;

  & label {
    grid-area: label;
  }

  & output {
    grid-area: output;
  }

  & .react-aria-SliderTrack {
    grid-area: track;
    position: relative;

    /* track line */
    &:before {
      content: '';
      display: block;
      position: absolute;
      background: gray;
    }
  }

  & .react-aria-SliderThumb {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: slateblue;
    border: 2px solid var(--spectrum-alias-background-color-default);

    &[data-dragging] {
      background: #4837b5;
    }

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue;
    }
  }

  &[data-orientation=horizontal] {
    flex-direction: column;
    width: 300px;

    & .react-aria-SliderTrack {
      height: 30px;
      width: 100%;

      &:before {
        height: 3px;
        width: 100%;
        top: 50%;
        transform: translateY(-50%);
      }
    }

    & .react-aria-SliderThumb {
      top: 50%;
    }
  }

  &[data-orientation=vertical] {
    height: 150px;
    display: block;

    & label,
    & output {
      display: none;
    }

    & .react-aria-SliderTrack {
      width: 30px;
      height: 100%;

      &:before {
        width: 3px;
        height: 100%;
        left: 50%;
        transform: translateX(-50%);
      }
    }

    & .react-aria-SliderThumb {
      left: 50%;
    }
  }

  &[data-disabled] {
    & .react-aria-SliderTrack {
      opacity: 0.4;
    }
  }
}
.react-aria-Slider {
  display: grid;
  grid-template-areas: "label output"
                       "track track";
  grid-template-columns: 1fr auto;
  max-width: 300px;

  & label {
    grid-area: label;
  }

  & output {
    grid-area: output;
  }

  & .react-aria-SliderTrack {
    grid-area: track;
    position: relative;

    /* track line */
    &:before {
      content: '';
      display: block;
      position: absolute;
      background: gray;
    }
  }

  & .react-aria-SliderThumb {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: slateblue;
    border: 2px solid var(--spectrum-alias-background-color-default);

    &[data-dragging] {
      background: #4837b5;
    }

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue;
    }
  }

  &[data-orientation=horizontal] {
    flex-direction: column;
    width: 300px;

    & .react-aria-SliderTrack {
      height: 30px;
      width: 100%;

      &:before {
        height: 3px;
        width: 100%;
        top: 50%;
        transform: translateY(-50%);
      }
    }

    & .react-aria-SliderThumb {
      top: 50%;
    }
  }

  &[data-orientation=vertical] {
    height: 150px;
    display: block;

    & label,
    & output {
      display: none;
    }

    & .react-aria-SliderTrack {
      width: 30px;
      height: 100%;

      &:before {
        width: 3px;
        height: 100%;
        left: 50%;
        transform: translateX(-50%);
      }
    }

    & .react-aria-SliderThumb {
      left: 50%;
    }
  }

  &[data-disabled] {
    & .react-aria-SliderTrack {
      opacity: 0.4;
    }
  }
}

Features#


The <input type="range"> HTML element can be used to build a slider, however it is very difficult to style cross browser. Slider helps achieve accessible sliders that can be styled as needed.

  • Customizable – Support for one or multiple thumbs, in both horizontal and vertical orientations. The whole slider, or individual thumbs can be disabled. Custom minimum, maximum, and step values are supported as well.
  • High quality interactions – Mouse, touch, and keyboard input is supported via the useMove hook. Pressing the track moves the nearest thumb to that position. Text selection and touch scrolling are prevented while dragging.
  • Touch friendly – Multiple thumbs or sliders can be dragged at once on multi-touch screens.
  • Accessible – Slider thumbs use visually hidden <input> elements for mobile screen reader support, and HTML form integration. <label> and <output> elements are automatically associated to provide context for assistive technologies.
  • International – Output value is formatted as a percentage or custom format according to the user's locale. The slider automatically mirrors all interactions in right-to-left languages.

Anatomy#


OutputLabelLabelLabel24TrackThumbGroup

Sliders consist of a track element showing the range of available values, one or more thumbs showing the current values, an optional <output> element displaying the current values textually, and a label. The thumbs can be dragged to allow a user to change their value. In addition, the track can be clicked to move the nearest thumb to that position.

If there is no visual label, an aria-label or aria-labelledby prop must be passed instead to identify the slider and thumbs to screen readers.

Composed components#

A Slider uses the following components, which may also be used standalone or reused in other components.

Label
A label provides context for an input element.

Props#


Slider#

NameTypeDefaultDescription
formatOptionsIntl.NumberFormatOptionsThe display format of the value label.
orientationOrientation'horizontal'The orientation of the Slider.
isDisabledbooleanWhether the whole Slider is disabled.
minValuenumber0The slider's minimum value.
maxValuenumber100The slider's maximum value.
stepnumber1The slider's step value.
valueTThe current value (controlled).
defaultValueTThe default value (uncontrolled).
labelReactNodeThe content to display as the label.
childrenReactNode( (values: SliderState )) => ReactNode
classNamestring( (values: SliderState )) => string
styleCSSProperties( (values: SliderState )) => CSSProperties
Events
NameTypeDefaultDescription
onChangeEnd( (value: T )) => voidFired when the slider stops moving, due to being let go.
onChange( (value: T )) => voidHandler that is called when the value changes.
Accessibility
NameTypeDefaultDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

Label#

A <Label> accepts all HTML attributes.

SliderOutput#

A <SliderOutput> renders the current value of the slider as text.

Show props
NameTypeDefaultDescription
childrenReactNode( (values: SliderState )) => ReactNode
classNamestring( (values: SliderState )) => string
styleCSSProperties( (values: SliderState )) => CSSProperties

SliderTrack#

The <SliderTrack> component is a grouping of one or more <SliderThumb> elements.

Show props
NameTypeDefaultDescription
childrenReactNode( (values: SliderState )) => ReactNode
classNamestring( (values: SliderState )) => string
styleCSSProperties( (values: SliderState )) => CSSProperties

SliderThumb#

The <SliderThumb> component renders an individual thumb within a <SliderTrack>.

Show props
NameTypeDefaultDescription
isDisabledbooleanWhether the Thumb is disabled.
indexnumber0Index of the thumb within the slider.
autoFocusbooleanWhether the element should receive focus on render.
validationStateValidationStateWhether the input should display its "valid" or "invalid" visual styling.
isRequiredboolean

Whether user input is required on the input before form submission. Often paired with the necessityIndicator prop to add a visual indicator to the input.

labelReactNodeThe content to display as the label.
childrenReactNode( (values: SliderThumbRenderProps )) => ReactNode
classNamestring( (values: SliderThumbRenderProps )) => string
styleCSSProperties( (values: SliderThumbRenderProps )) => CSSProperties
Events
NameTypeDefaultDescription
onFocus( (e: FocusEvent )) => voidHandler that is called when the element receives focus.
onBlur( (e: FocusEvent )) => voidHandler that is called when the element loses focus.
onFocusChange( (isFocused: boolean )) => voidHandler that is called when the element's focus status changes.
onKeyDown( (e: KeyboardEvent )) => voidHandler that is called when a key is pressed.
onKeyUp( (e: KeyboardEvent )) => voidHandler that is called when a key is released.
Accessibility
NameTypeDefaultDescription
idstringThe element's unique identifier. See MDN.
excludeFromTabOrderboolean

Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available.

aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.
aria-errormessagestringIdentifies the element that provides an error message for the object.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-Slider {
  /* ... */
}
.react-aria-Slider {
  /* ... */
}
.react-aria-Slider {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<Slider className="my-slider">
  {/* ... */}
</Slider>
<Slider className="my-slider">
  {/* ... */}
</Slider>
<Slider className="my-slider">
  {/* ... */}
</Slider>;

In addition, some components support multiple UI states (e.g. focused, placeholder, readonly, etc.). React Aria components expose states using DOM attributes, which you can target in CSS selectors. These are ARIA attributes wherever possible, or data attributes when a relevant ARIA attribute does not exist. For example:

.react-aria-SliderThumb[data-dragging] {
  /* ... */
}

.react-aria-SliderThumb[data-focused] {
  /* ... */
}
.react-aria-SliderThumb[data-dragging] {
  /* ... */
}

.react-aria-SliderThumb[data-focused] {
  /* ... */
}
.react-aria-SliderThumb[data-dragging] {
  /* ... */
}

.react-aria-SliderThumb[data-focused] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<SliderThumb
  className={({ isDragging }) => isDragging ? 'bg-gray-700' : 'bg-gray-600'}
/>;
<SliderThumb
  className={({ isDragging }) =>
    isDragging ? 'bg-gray-700' : 'bg-gray-600'}
/>;
<SliderThumb
  className={(
    { isDragging }
  ) =>
    isDragging
      ? 'bg-gray-700'
      : 'bg-gray-600'}
/>;

Render props may also be used as children to alter what elements are rendered based on the current state. For example, you implement custom formatting for the slider's current value.

<SliderOutput>
  {state => `${state.getThumbValueLabel(0)} cookies`}
</SliderOutput>
<SliderOutput>
  {state => `${state.getThumbValueLabel(0)} cookies`}
</SliderOutput>
<SliderOutput>
  {(state) =>
    `${
      state
        .getThumbValueLabel(
          0
        )
    } cookies`}
</SliderOutput>;

The states, selectors, and render props for each component used in a Slider are documented below.

Slider#

The Slider component can be targeted with the .react-aria-Slider CSS selector, or by overriding with a custom className. It provides a SliderState object to its render props, which can be used to customize the className, style, or children. In addition, it provides the following states as DOM attributes.

NameCSS SelectorDescription
orientation[data-orientation="horizontal | vertical"]The orientation of the slider.
isDisabled[data-disabled]Whether the slider is disabled.

Label#

A Label can be targeted with the .react-aria-Label CSS selector, or by overriding with a custom className.

SliderOutput#

The SliderOutput component can be targeted with the .react-aria-SliderOutput CSS selector, or by overriding with a custom className. It provides a SliderState object to its render props, which can be used to customize the className, style, or children.

SliderTrack#

The SliderTrack component can be targeted with the .react-aria-SliderTrack CSS selector, or by overriding with a custom className. It provides a SliderState object to its render props, which can be used to customize the className, style, or children.

SliderThumb#

The SliderThumb component can be targeted with the .react-aria-SliderThumb CSS selector, or by overriding with a custom className. It provides the following render props and states:

NameCSS SelectorDescription
stateThe slider state object.
isDragging[data-dragging]Whether this thumb is currently being dragged.
isFocused[data-focused]Whether the thumb is currently focused.
isFocusVisible[data-focus-visible]Whether the thumb is keyboard focused.
isDisabled[data-disabled]Whether the thumb is disabled.

Reusable wrappers#


If you will use a Slider in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps Slider and all of its children together into a single component which accepts a label prop, which is passed to the right place. It also shows how to use the SliderTrack and SliderOutput render props to render multiple slider thumbs, depending on the provided values.

function MySlider({ label, ...props }) {
  return (
    <Slider {...props}>
      <Label>{label}</Label>
      <SliderOutput>
        {(state) =>
          state.values.map((_, i) => state.getThumbValueLabel(i)).join(' – ')}
      </SliderOutput>
      <SliderTrack>
        {(state) =>
          state.values.map((_, i) => <SliderThumb key={i} index={i} />)}
      </SliderTrack>
    </Slider>
  );
}

<MySlider label="Range" defaultValue={[30, 60]} />
function MySlider({ label, ...props }) {
  return (
    <Slider {...props}>
      <Label>{label}</Label>
      <SliderOutput>
        {(state) =>
          state.values.map((_, i) =>
            state.getThumbValueLabel(i)
          ).join(' – ')}
      </SliderOutput>
      <SliderTrack>
        {(state) =>
          state.values.map((_, i) => (
            <SliderThumb key={i} index={i} />
          ))}
      </SliderTrack>
    </Slider>
  );
}

<MySlider label="Range" defaultValue={[30, 60]} />
function MySlider(
  { label, ...props }
) {
  return (
    <Slider {...props}>
      <Label>
        {label}
      </Label>
      <SliderOutput>
        {(state) =>
          state.values
            .map((
              _,
              i
            ) =>
              state
                .getThumbValueLabel(
                  i
                )
            ).join(
              ' – '
            )}
      </SliderOutput>
      <SliderTrack>
        {(state) =>
          state.values
            .map((
              _,
              i
            ) => (
              <SliderThumb
                key={i}
                index={i}
              />
            ))}
      </SliderTrack>
    </Slider>
  );
}

<MySlider
  label="Range"
  defaultValue={[
    30,
    60
  ]}
/>

Usage#


The following examples show how to use the MySlider component created in the above examples.

Vertical orientation#

Sliders are horizontally oriented by default. The orientation prop can be set to "vertical" to create a vertical slider. This example also uses aria-label rather than label to create a slider with no visible label.

<MySlider
  orientation="vertical"
  aria-label="Opacity"
  maxValue={1}
  step={0.01} />
<MySlider
  orientation="vertical"
  aria-label="Opacity"
  maxValue={1}
  step={0.01} />
<MySlider
  orientation="vertical"
  aria-label="Opacity"
  maxValue={1}
  step={0.01} />

Controlled value#

The value prop paired with the onChange event can be used to make a slider controlled. The value must fall between the Slider's minimum and maximum values, which default to 0 and 100 respectively. The onChange event receives the new slider value as a parameter, which can be used to update state.

function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        value={value}
        onChange={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        value={value}
        onChange={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
function Example() {
  let [value, setValue] =
    React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        value={value}
        onChange={setValue}
      />
      <p>
        Current value:
        {' '}
        {value}
      </p>
    </>
  );
}

Multi thumb sliders specify their values as an array rather than a single number.

function Example() {
  let [value, setValue] = React.useState([25, 75]);
  return (
    <>
      <MySlider
        label="Range"
        value={value}
        onChange={setValue} />
      <p>Current value: {value.join(' – ')}</p>
    </>
  );
}
function Example() {
  let [value, setValue] = React.useState([25, 75]);
  return (
    <>
      <MySlider
        label="Range"
        value={value}
        onChange={setValue} />
      <p>Current value: {value.join(' – ')}</p>
    </>
  );
}
function Example() {
  let [value, setValue] =
    React.useState([
      25,
      75
    ]);
  return (
    <>
      <MySlider
        label="Range"
        value={value}
        onChange={setValue}
      />
      <p>
        Current value:
        {' '}
        {value.join(
          ' – '
        )}
      </p>
    </>
  );
}

onChangeEnd#

The onChangeEnd prop can be used to handle when a user stops dragging a slider, whereas the onChange prop is called as the user drags.

function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        defaultValue={value}
        onChangeEnd={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
function Example() {
  let [value, setValue] = React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        defaultValue={value}
        onChangeEnd={setValue} />
      <p>Current value: {value}</p>
    </>
  );
}
function Example() {
  let [value, setValue] =
    React.useState(25);
  return (
    <>
      <MySlider
        label="Cookies to buy"
        defaultValue={value}
        onChangeEnd={setValue}
      />
      <p>
        Current value:
        {' '}
        {value}
      </p>
    </>
  );
}

Custom value scale#

By default, slider values are precentages between 0 and 100. A different scale can be used by setting the minValue and maxValue props.

<MySlider
  label="Cookies to buy"
  minValue={50}
  maxValue={150}
  defaultValue={100} />
<MySlider
  label="Cookies to buy"
  minValue={50}
  maxValue={150}
  defaultValue={100} />
<MySlider
  label="Cookies to buy"
  minValue={50}
  maxValue={150}
  defaultValue={100} />

Value formatting#

Values are formatted as a percentage by default, but this can be modified by using the formatOptions prop to specify a different format. formatOptions is compatible with the option parameter of Intl.NumberFormat and is applied based on the current locale.

<MySlider
  label="Currency"
  formatOptions={{style: 'currency', currency: 'JPY'}}
  defaultValue={60} />
<MySlider
  label="Currency"
  formatOptions={{style: 'currency', currency: 'JPY'}}
  defaultValue={60} />
<MySlider
  label="Currency"
  formatOptions={{
    style: 'currency',
    currency: 'JPY'
  }}
  defaultValue={60}
/>

Step values#

The step prop can be used to snap the value to certain increments. The steps are calculated starting from the minimum. For example, if minValue={2}, and step={3}, the valid step values would be 2, 5, 8, 11, etc. This example allows increments of 5 between 0 and 100.

<MySlider
  label="Amount"
  formatOptions={{style: 'currency', currency: 'USD'}}
  minValue={0}
  maxValue={100}
  step={5} />
<MySlider
  label="Amount"
  formatOptions={{style: 'currency', currency: 'USD'}}
  minValue={0}
  maxValue={100}
  step={5} />
<MySlider
  label="Amount"
  formatOptions={{
    style: 'currency',
    currency: 'USD'
  }}
  minValue={0}
  maxValue={100}
  step={5}
/>

Disabled#

A slider can be disabled using the isDisabled prop.

<MySlider
  label="Cookies to share"
  defaultValue={25}
  isDisabled />
<MySlider
  label="Cookies to share"
  defaultValue={25}
  isDisabled />
<MySlider
  label="Cookies to share"
  defaultValue={25}
  isDisabled
/>

Advanced customization#


Composition#

If you need to customize one of the components within a Slider, such as Label or SliderOutput, in many cases you can create a wrapper component. This lets you customize the props passed to the component.

function MySliderOutput(props) {
  return <MySliderOutput {...props} className="my-slider-output" />
}
function MySliderOutput(props) {
  return (
    <MySliderOutput
      {...props}
      className="my-slider-output"
    />
  );
}
function MySliderOutput(
  props
) {
  return (
    <MySliderOutput
      {...props}
      className="my-slider-output"
    />
  );
}

Hooks#

If you need to customize things even further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See useSlider for more details.