Slider
A slider allows a user to select one or more values within a range.
install | yarn add react-aria-components |
---|---|
version | 1.0.0-beta.2 |
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 {
--label-color: var(--spectrum-alias-text-color);
--track-color: gray;
--thumb-color: slateblue;
--thumb-color-dragging: lch(from slateblue calc(l - 10%) c h);
--focus-ring-color: slateblue;
display: grid;
grid-template-areas: "label output"
"track track";
grid-template-columns: 1fr auto;
max-width: 300px;
color: var(--label-color);
.react-aria-Label {
grid-area: label;
}
.react-aria-SliderOutput {
grid-area: output;
}
.react-aria-SliderTrack {
grid-area: track;
position: relative;
/* track line */
&:before {
content: '';
display: block;
position: absolute;
background: var(--track-color);
}
}
.react-aria-SliderThumb {
width: 1.429rem;
height: 1.429rem;
border-radius: 50%;
background: var(--thumb-color);
border: 2px solid var(--spectrum-alias-background-color-default);
&[data-dragging] {
background: var(--thumb-color-dragging);
}
&[data-focus-visible] {
box-shadow: 0 0 0 2px var(--focus-ring-color);
}
}
&[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;
.react-aria-Label,
.react-aria-SliderOutput {
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;
}
}
}
@media (forced-colors: active) {
.react-aria-Slider {
forced-color-adjust: none;
--label-color: ButtonText;
--track-color: ButtonBorder;
--thumb-color: ButtonBorder;
--thumb-color-dragging: Highlight;
--focus-ring-color: Highlight;
--spectrum-alias-background-color-default: Canvas;
&[data-disabled] {
--track-color: GrayText;
--thumb-color: GrayText;
--label-color: GrayText;
& .react-aria-SliderTrack {
opacity: 1;
}
}
}
}
.react-aria-Slider {
--label-color: var(--spectrum-alias-text-color);
--track-color: gray;
--thumb-color: slateblue;
--thumb-color-dragging: lch(from slateblue calc(l - 10%) c h);
--focus-ring-color: slateblue;
display: grid;
grid-template-areas: "label output"
"track track";
grid-template-columns: 1fr auto;
max-width: 300px;
color: var(--label-color);
.react-aria-Label {
grid-area: label;
}
.react-aria-SliderOutput {
grid-area: output;
}
.react-aria-SliderTrack {
grid-area: track;
position: relative;
/* track line */
&:before {
content: '';
display: block;
position: absolute;
background: var(--track-color);
}
}
.react-aria-SliderThumb {
width: 1.429rem;
height: 1.429rem;
border-radius: 50%;
background: var(--thumb-color);
border: 2px solid var(--spectrum-alias-background-color-default);
&[data-dragging] {
background: var(--thumb-color-dragging);
}
&[data-focus-visible] {
box-shadow: 0 0 0 2px var(--focus-ring-color);
}
}
&[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;
.react-aria-Label,
.react-aria-SliderOutput {
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;
}
}
}
@media (forced-colors: active) {
.react-aria-Slider {
forced-color-adjust: none;
--label-color: ButtonText;
--track-color: ButtonBorder;
--thumb-color: ButtonBorder;
--thumb-color-dragging: Highlight;
--focus-ring-color: Highlight;
--spectrum-alias-background-color-default: Canvas;
&[data-disabled] {
--track-color: GrayText;
--thumb-color: GrayText;
--label-color: GrayText;
& .react-aria-SliderTrack {
opacity: 1;
}
}
}
}
.react-aria-Slider {
--label-color: var(--spectrum-alias-text-color);
--track-color: gray;
--thumb-color: slateblue;
--thumb-color-dragging: lch(from slateblue calc(l - 10%) c h);
--focus-ring-color: slateblue;
display: grid;
grid-template-areas: "label output"
"track track";
grid-template-columns: 1fr auto;
max-width: 300px;
color: var(--label-color);
.react-aria-Label {
grid-area: label;
}
.react-aria-SliderOutput {
grid-area: output;
}
.react-aria-SliderTrack {
grid-area: track;
position: relative;
/* track line */
&:before {
content: '';
display: block;
position: absolute;
background: var(--track-color);
}
}
.react-aria-SliderThumb {
width: 1.429rem;
height: 1.429rem;
border-radius: 50%;
background: var(--thumb-color);
border: 2px solid var(--spectrum-alias-background-color-default);
&[data-dragging] {
background: var(--thumb-color-dragging);
}
&[data-focus-visible] {
box-shadow: 0 0 0 2px var(--focus-ring-color);
}
}
&[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;
.react-aria-Label,
.react-aria-SliderOutput {
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;
}
}
}
@media (forced-colors: active) {
.react-aria-Slider {
forced-color-adjust: none;
--label-color: ButtonText;
--track-color: ButtonBorder;
--thumb-color: ButtonBorder;
--thumb-color-dragging: Highlight;
--focus-ring-color: Highlight;
--spectrum-alias-background-color-default: Canvas;
&[data-disabled] {
--track-color: GrayText;
--thumb-color: GrayText;
--label-color: GrayText;
& .react-aria-SliderTrack {
opacity: 1;
}
}
}
}
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.
import {Label, Slider, SliderOutput, SliderThumb, SliderTrack} from 'react-aria-components';
<Slider>
<Label />
<SliderOutput />
<SliderTrack>
<SliderThumb />
<SliderThumb>
<Label />
</SliderThumb>
</SliderTrack>
</Slider>
import {
Label,
Slider,
SliderOutput,
SliderThumb,
SliderTrack
} from 'react-aria-components';
<Slider>
<Label />
<SliderOutput />
<SliderTrack>
<SliderThumb />
<SliderThumb>
<Label />
</SliderThumb>
</SliderTrack>
</Slider>
import {
Label,
Slider,
SliderOutput,
SliderThumb,
SliderTrack
} from 'react-aria-components';
<Slider>
<Label />
<SliderOutput />
<SliderTrack>
<SliderThumb />
<SliderThumb>
<Label />
</SliderThumb>
</SliderTrack>
</Slider>
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.
Examples#
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. When multiple thumbs are rendered, each SliderThumb
should have an aria-label
, which is provided via the thumbLabels
prop in this example.
import type {SliderProps} from 'react-aria-components';
interface MySliderProps<T> extends SliderProps<T> {
label?: string;
thumbLabels?: string[];
}
function MySlider<T extends number | number[]>(
{ label, thumbLabels, ...props }: MySliderProps<T>
) {
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} aria-label={thumbLabels?.[i]} />
))}
</SliderTrack>
</Slider>
);
}
<MySlider
label="Range"
defaultValue={[30, 60]}
thumbLabels={['start', 'end']}
/>
import type {SliderProps} from 'react-aria-components';
interface MySliderProps<T> extends SliderProps<T> {
label?: string;
thumbLabels?: string[];
}
function MySlider<T extends number | number[]>(
{ label, thumbLabels, ...props }: MySliderProps<T>
) {
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}
aria-label={thumbLabels?.[i]}
/>
))}
</SliderTrack>
</Slider>
);
}
<MySlider
label="Range"
defaultValue={[30, 60]}
thumbLabels={['start', 'end']}
/>
import type {SliderProps} from 'react-aria-components';
interface MySliderProps<
T
> extends
SliderProps<T> {
label?: string;
thumbLabels?: string[];
}
function MySlider<
T extends
| number
| number[]
>(
{
label,
thumbLabels,
...props
}: MySliderProps<T>
) {
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}
aria-label={thumbLabels
?.[i]}
/>
))}
</SliderTrack>
</Slider>
);
}
<MySlider
label="Range"
defaultValue={[
30,
60
]}
thumbLabels={[
'start',
'end'
]}
/>
Value#
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<number>
label="Cookies to buy"
value={value}
onChange={setValue} />
<p>Current value: {value}</p>
</>
);
}
function Example() {
let [value, setValue] = React.useState(25);
return (
<>
<MySlider<number>
label="Cookies to buy"
value={value}
onChange={setValue} />
<p>Current value: {value}</p>
</>
);
}
function Example() {
let [value, setValue] =
React.useState(25);
return (
<>
<MySlider<number>
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<number[]>
label="Range"
thumbLabels={['start', 'end']}
value={value}
onChange={setValue} />
<p>Current value: {value.join(' – ')}</p>
</>
);
}
function Example() {
let [value, setValue] = React.useState([25, 75]);
return (
<>
<MySlider<number[]>
label="Range"
thumbLabels={['start', 'end']}
value={value}
onChange={setValue} />
<p>Current value: {value.join(' – ')}</p>
</>
);
}
function Example() {
let [value, setValue] =
React.useState([
25,
75
]);
return (
<>
<MySlider<number[]>
label="Range"
thumbLabels={[
'start',
'end'
]}
value={value}
onChange={setValue}
/>
<p>
Current value:
{' '}
{value.join(
' – '
)}
</p>
</>
);
}
HTML forms#
Each SliderThumb supports the name
prop for integration with HTML forms.
<Slider defaultValue={50}>
<Label>Opacity</Label>
<SliderOutput />
<SliderTrack>
<SliderThumb name="opacity" /> </SliderTrack>
</Slider>
<Slider defaultValue={50}>
<Label>Opacity</Label>
<SliderOutput />
<SliderTrack>
<SliderThumb name="opacity" /> </SliderTrack>
</Slider>
<Slider
defaultValue={50}
>
<Label>
Opacity
</Label>
<SliderOutput />
<SliderTrack>
<SliderThumb name="opacity" /> </SliderTrack>
</Slider>
Events#
The onChange
prop is called as the user drags a slider. The onChangeEnd
prop can be used to handle when a user stops dragging.
function Example() {
let [value, setValue] = React.useState(25);
return (
<>
<MySlider<number>
label="Cookies to buy"
defaultValue={value}
onChangeEnd={setValue} />
<p>Current value: {value}</p>
</>
);
}
function Example() {
let [value, setValue] = React.useState(25);
return (
<>
<MySlider<number>
label="Cookies to buy"
defaultValue={value}
onChangeEnd={setValue} />
<p>Current value: {value}</p>
</>
);
}
function Example() {
let [value, setValue] =
React.useState(25);
return (
<>
<MySlider<number>
label="Cookies to buy"
defaultValue={value}
onChangeEnd={setValue}
/>
<p>
Current value:
{' '}
{value}
</p>
</>
);
}
Validation#
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} />
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}
/>
Visual options#
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} />
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}
/>
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
/>
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). |
children | ReactNode | (
(values: SliderRenderProps
)) => ReactNode | — | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: SliderRenderProps
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: SliderRenderProps
)) => CSSProperties | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | 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. |
Layout
Name | Type | Description |
slot | string | null | A slot name for the component. Slots allow the component to receive props from a parent component.
An explicit |
Accessibility
Name | Type | 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.
Name | Type | Description |
children | ReactNode | (
(values: SliderRenderProps
)) => ReactNode | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: SliderRenderProps
)) => string | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: SliderRenderProps
)) => CSSProperties | The inline style for the element. A function may be provided to compute the style based on component state. |
SliderTrack#
The <SliderTrack>
component is a grouping of one or more <SliderThumb>
elements.
Name | Type | Description |
children | ReactNode | (
(values: SliderTrackRenderProps
)) => ReactNode | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: SliderTrackRenderProps
)) => string | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: SliderTrackRenderProps
)) => CSSProperties | The inline style for the element. A function may be provided to compute the style based on component state. |
SliderThumb#
The <SliderThumb>
component renders an individual thumb within a <SliderTrack>
.
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. |
isRequired | boolean | — | Whether user input is required on the input before form submission. |
isInvalid | boolean | — | Whether the input value is invalid. |
label | ReactNode | — | The content to display as the label. |
name | string | — | The name of the input element, used when submitting an HTML form. See MDN. |
children | ReactNode | (
(values: SliderThumbRenderProps
)) => ReactNode | — | The children of the component. A function may be provided to alter the children based on component state. |
className | string | (
(values: SliderThumbRenderProps
)) => string | — | The CSS className for the element. A function may be provided to compute the class based on component state. |
style | CSSProperties | (
(values: SliderThumbRenderProps
)) => CSSProperties | — | The inline style for the element. A function may be provided to compute the style based on component state. |
Events
Name | Type | Description |
onFocus | (
(e: FocusEvent<Target>
)) => void | Handler that is called when the element receives focus. |
onBlur | (
(e: FocusEvent<Target>
)) => 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 | 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 data attributes, which you can target in CSS selectors. 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 supports the following states:
Name | CSS Selector | Description |
orientation | [data-orientation="horizontal | vertical"] | The orientation of the slider. |
isDisabled | [data-disabled] | Whether the slider is disabled. |
state | — | State of the slider. |
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 supports the following states:
Name | CSS Selector | Description |
orientation | [data-orientation="horizontal | vertical"] | The orientation of the slider. |
isDisabled | [data-disabled] | Whether the slider is disabled. |
state | — | State of the slider. |
SliderTrack#
The SliderTrack
component can be targeted with the .react-aria-SliderTrack
CSS selector, or by overriding with a custom className
. It supports the following states:
Name | CSS Selector | Description |
isHovered | [data-hovered] | Whether the slider track is currently hovered with a mouse. |
orientation | [data-orientation="horizontal | vertical"] | The orientation of the slider. |
isDisabled | [data-disabled] | Whether the slider is disabled. |
state | — | State of the slider. |
SliderThumb#
The SliderThumb
component can be targeted with the .react-aria-SliderThumb
CSS selector, or by overriding with a custom className
. It supports the following states:
Name | CSS Selector | Description |
state | — | State of the slider. |
isDragging | [data-dragging] | Whether this thumb is currently being dragged. |
isHovered | [data-hovered] | Whether the thumb is currently hovered with a mouse. |
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. |
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 <SliderOutput {...props} className="my-slider-output" />
}
function MySliderOutput(props) {
return (
<SliderOutput {...props} className="my-slider-output" />
);
}
function MySliderOutput(
props
) {
return (
<SliderOutput
{...props}
className="my-slider-output"
/>
);
}
Contexts#
All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in mergeProps).
Component | Context | Props | Ref |
Slider | SliderContext | SliderProps | HTMLDivElement |
This example shows a SliderDescription
component that accepts a slider in its children and renders a description element below it. It uses the useId hook to generate a unique id for the description, and associates it with the slider via the aria-describedby
attribute passed to the SwitchContext
provider.
import {SliderContext} from 'react-aria-components';
import {useId} from 'react-aria';
interface SliderDescriptionProps {
children?: React.ReactNode;
description?: string;
}
function SliderDescription({ children, description }: SliderDescriptionProps) {
let descriptionId = useId();
return (
<div>
<SliderContext.Provider value={{ 'aria-describedby': descriptionId }}> {children}
</SliderContext.Provider>
<small id={descriptionId}>{description}</small>
</div>
);
}
<SliderDescription description="Keeping your display on may shorten its life.">
<MySlider
label="Turn off display after"
minValue={10}
maxValue={60}
defaultValue={45}
formatOptions={{ style: 'unit', unit: 'minute' }}
/>
</SliderDescription>
import {SliderContext} from 'react-aria-components';
import {useId} from 'react-aria';
interface SliderDescriptionProps {
children?: React.ReactNode;
description?: string;
}
function SliderDescription(
{ children, description }: SliderDescriptionProps
) {
let descriptionId = useId();
return (
<div>
<SliderContext.Provider
value={{ 'aria-describedby': descriptionId }}
> {children}
</SliderContext.Provider>
<small id={descriptionId}>{description}</small>
</div>
);
}
<SliderDescription description="Keeping your display on may shorten its life.">
<MySlider
label="Turn off display after"
minValue={10}
maxValue={60}
defaultValue={45}
formatOptions={{ style: 'unit', unit: 'minute' }}
/>
</SliderDescription>
import {SliderContext} from 'react-aria-components';
import {useId} from 'react-aria';
interface SliderDescriptionProps {
children?:
React.ReactNode;
description?: string;
}
function SliderDescription(
{
children,
description
}: SliderDescriptionProps
) {
let descriptionId =
useId();
return (
<div>
<SliderContext.Provider
value={{
'aria-describedby':
descriptionId
}}
> {children}
</SliderContext.Provider>
<small
id={descriptionId}
>
{description}
</small>
</div>
);
}
<SliderDescription description="Keeping your display on may shorten its life.">
<MySlider
label="Turn off display after"
minValue={10}
maxValue={60}
defaultValue={45}
formatOptions={{
style: 'unit',
unit: 'minute'
}}
/>
</SliderDescription>
Custom children#
Slider passes props to its child components, such as the label, via their associated contexts. These contexts are exported so you can also consume them in your own custom components. This enables you to reuse existing components from your app or component library together with React Aria Components.
Component | Context | Props | Ref |
Label | LabelContext | LabelProps | HTMLLabelElement |
This example consumes from LabelContext
in an existing styled label component to make it compatible with React Aria Components. The useContextProps
hook merges the local props and ref with the ones provided via context by Slider.
import type {LabelProps} from 'react-aria-components';
import {LabelContext, useContextProps} from 'react-aria-components';
const MyCustomLabel = React.forwardRef(
(props: LabelProps, ref: React.ForwardedRef<HTMLLabelElement>) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] = useContextProps(props, ref, LabelContext);
// ... your existing Label component
return <label {...props} ref={ref} />;
}
);
import type {LabelProps} from 'react-aria-components';
import {
LabelContext,
useContextProps
} from 'react-aria-components';
const MyCustomLabel = React.forwardRef(
(
props: LabelProps,
ref: React.ForwardedRef<HTMLLabelElement>
) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] = useContextProps(
props,
ref,
LabelContext
);
// ... your existing Label component
return <label {...props} ref={ref} />;
}
);
import type {LabelProps} from 'react-aria-components';
import {
LabelContext,
useContextProps
} from 'react-aria-components';
const MyCustomLabel =
React.forwardRef(
(
props: LabelProps,
ref:
React.ForwardedRef<
HTMLLabelElement
>
) => {
// Merge the local props and ref with the ones provided via context.
[props, ref] =
useContextProps(
props,
ref,
LabelContext
);
// ... your existing Label component
return (
<label
{...props}
ref={ref}
/>
);
}
);
Now you can use MyCustomLabel
within a Slider
, in place of the builtin React Aria Components Label
.
<Slider>
<MyCustomLabel>Opacity</MyCustomLabel> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
<Slider>
<MyCustomLabel>Opacity</MyCustomLabel> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
<Slider>
<MyCustomLabel>
Opacity
</MyCustomLabel> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
State#
Slider provides a SliderState
object to its children via SliderStateContext
. This can be used to access and manipulate the slider's state.
This example shows a SliderNumberField
component that can be placed within a Slider
to allow the user to enter a number and update the slider's value.
import {Input, LabelContext, NumberField, SliderStateContext, useSlottedContext} from 'react-aria-components';
function SliderNumberField() {
let state = React.useContext(SliderStateContext)!; let labelProps = useSlottedContext(LabelContext)!;
return (
<NumberField
aria-labelledby={labelProps.id}
value={state.values[0]}
onChange={(v) => state.setThumbValue(0, v)}
>
<Input />
</NumberField>
);
}
<Slider defaultValue={30}>
<Label>Opacity</Label>
<SliderNumberField /> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
import {
Input,
LabelContext,
NumberField,
SliderStateContext,
useSlottedContext
} from 'react-aria-components';
function SliderNumberField() {
let state = React.useContext(SliderStateContext)!; let labelProps = useSlottedContext(LabelContext)!;
return (
<NumberField
aria-labelledby={labelProps.id}
value={state.values[0]}
onChange={(v) => state.setThumbValue(0, v)}
>
<Input />
</NumberField>
);
}
<Slider defaultValue={30}>
<Label>Opacity</Label>
<SliderNumberField /> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
import {
Input,
LabelContext,
NumberField,
SliderStateContext,
useSlottedContext
} from 'react-aria-components';
function SliderNumberField() {
let state = React
.useContext(
SliderStateContext
)!; let labelProps =
useSlottedContext(
LabelContext
)!;
return (
<NumberField
aria-labelledby={labelProps
.id}
value={state
.values[0]}
onChange={(v) =>
state
.setThumbValue(
0,
v
)}
>
<Input />
</NumberField>
);
}
<Slider
defaultValue={30}
>
<Label>
Opacity
</Label>
<SliderNumberField /> <SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
Show CSS
.react-aria-Input {
--field-border: var(--spectrum-alias-border-color);
--field-background: var(--spectrum-global-color-gray-50);
--text-color: var(--spectrum-alias-text-color);
--focus-ring-color: slateblue;
padding: 0.286rem;
margin: 0;
border: 1px solid var(--field-border);
border-radius: 6px;
background: var(--field-background);
font-size: 1rem;
color: var(--text-color);
width: 3ch;
&[data-focused] {
outline: none;
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
}
@media (forced-colors: active) {
.react-aria-Input {
--field-border: ButtonBorder;
--field-background: Field;
--text-color: FieldText;
--focus-ring-color: Highlight;
}
}
.react-aria-Input {
--field-border: var(--spectrum-alias-border-color);
--field-background: var(--spectrum-global-color-gray-50);
--text-color: var(--spectrum-alias-text-color);
--focus-ring-color: slateblue;
padding: 0.286rem;
margin: 0;
border: 1px solid var(--field-border);
border-radius: 6px;
background: var(--field-background);
font-size: 1rem;
color: var(--text-color);
width: 3ch;
&[data-focused] {
outline: none;
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
}
@media (forced-colors: active) {
.react-aria-Input {
--field-border: ButtonBorder;
--field-background: Field;
--text-color: FieldText;
--focus-ring-color: Highlight;
}
}
.react-aria-Input {
--field-border: var(--spectrum-alias-border-color);
--field-background: var(--spectrum-global-color-gray-50);
--text-color: var(--spectrum-alias-text-color);
--focus-ring-color: slateblue;
padding: 0.286rem;
margin: 0;
border: 1px solid var(--field-border);
border-radius: 6px;
background: var(--field-background);
font-size: 1rem;
color: var(--text-color);
width: 3ch;
&[data-focused] {
outline: none;
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
}
@media (forced-colors: active) {
.react-aria-Input {
--field-border: ButtonBorder;
--field-background: Field;
--text-color: FieldText;
--focus-ring-color: Highlight;
}
}
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.