Slider
A slider allows a user to select one or more values within a range.
install | yarn add react-aria-components |
---|---|
version | 3.17.0 |
usage | import {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#
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.
Props#
Slider#
Name | Type | Default | Description |
formatOptions | Intl.NumberFormatOptions | — | The display format of the value label. |
orientation | Orientation | 'horizontal' | The orientation of the Slider. |
isDisabled | boolean | — | Whether the whole Slider is disabled. |
minValue | number | 0 | The slider's minimum value. |
maxValue | number | 100 | The slider's maximum value. |
step | number | 1 | The slider's step value. |
value | T | — | The current value (controlled). |
defaultValue | T | — | The default value (uncontrolled). |
label | ReactNode | — | The content to display as the label. |
children | ReactNode | (
(values: SliderState
)) => ReactNode | — | |
className | string | (
(values: SliderState
)) => string | — | |
style | CSSProperties | (
(values: SliderState
)) => CSSProperties | — |
Events
Name | Type | Default | Description |
onChangeEnd | (
(value: T
)) => void | — | Fired when the slider stops moving, due to being let go. |
onChange | (
(value: T
)) => void | — | Handler that is called when the value changes. |
Accessibility
Name | Type | Default | Description |
id | string | — | The element's unique identifier. See MDN. |
aria-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-details | string | — | Identifies 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
Name | Type | Default | Description |
children | ReactNode | (
(values: SliderState
)) => ReactNode | — | |
className | string | (
(values: SliderState
)) => string | — | |
style | CSSProperties | (
(values: SliderState
)) => CSSProperties | — |
SliderTrack#
The <SliderTrack>
component is a grouping of one or more <SliderThumb>
elements.
Show props
Name | Type | Default | Description |
children | ReactNode | (
(values: SliderState
)) => ReactNode | — | |
className | string | (
(values: SliderState
)) => string | — | |
style | CSSProperties | (
(values: SliderState
)) => CSSProperties | — |
SliderThumb#
The <SliderThumb>
component renders an individual thumb within a <SliderTrack>
.
Show props
Name | Type | Default | Description |
isDisabled | boolean | — | Whether the Thumb is disabled. |
index | number | 0 | Index of the thumb within the slider. |
autoFocus | boolean | — | Whether the element should receive focus on render. |
validationState | ValidationState | — | Whether the input should display its "valid" or "invalid" visual styling. |
isRequired | boolean | — | Whether user input is required on the input before form submission.
Often paired with the |
label | ReactNode | — | The content to display as the label. |
children | ReactNode | (
(values: SliderThumbRenderProps
)) => ReactNode | — | |
className | string | (
(values: SliderThumbRenderProps
)) => string | — | |
style | CSSProperties | (
(values: SliderThumbRenderProps
)) => CSSProperties | — |
Events
Name | Type | Default | Description |
onFocus | (
(e: FocusEvent
)) => void | — | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent
)) => void | — | Handler that is called when the element loses focus. |
onFocusChange | (
(isFocused: boolean
)) => void | — | Handler that is called when the element's focus status changes. |
onKeyDown | (
(e: KeyboardEvent
)) => void | — | Handler that is called when a key is pressed. |
onKeyUp | (
(e: KeyboardEvent
)) => void | — | Handler that is called when a key is released. |
Accessibility
Name | Type | Default | Description |
id | string | — | The element's unique identifier. See MDN. |
excludeFromTabOrder | boolean | — | 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-label | string | — | Defines a string value that labels the current element. |
aria-labelledby | string | — | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | — | Identifies the element (or elements) that describes the object. |
aria-details | string | — | Identifies the element (or elements) that provide a detailed, extended description for the object. |
aria-errormessage | string | — | Identifies 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 => ` cookies`}
</SliderOutput>
<SliderOutput>
{state => ` cookies`}
</SliderOutput>
<SliderOutput>
{(state) =>
` 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.
Name | CSS Selector | Description |
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:
Name | CSS Selector | Description |
state | — | The 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.