packages/react-aria-components/docs/Slider.mdx
{/* Copyright 2020 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}
import {Layout} from '@react-spectrum/docs'; export default Layout;
import docs from 'docs:react-aria-components'; import statelyDocs from 'docs:@react-stately/slider'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import Anatomy from '/packages/react-aria/docs/slider/anatomy.svg'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Divider} from '@react-spectrum/divider'; import {ExampleList} from '@react-spectrum/docs/src/ExampleList'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import Label from '@react-spectrum/docs/pages/assets/component-illustrations/Label.svg'; import {Keyboard} from '@react-spectrum/text'; import {StarterKits} from '@react-spectrum/docs/src/StarterKits';
<PageDescription>{docs.exports.Slider.description}</PageDescription>
<HeaderInfo packageData={packageData} componentNames={['Slider']} sourceData={[ {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/slider/'} ]} />
import {Slider, Label, SliderOutput, SliderTrack, SliderThumb} from 'react-aria-components';
<Slider defaultValue={30}>
<Label>Opacity</Label>
<SliderOutput />
<SliderTrack>
<SliderThumb />
</SliderTrack>
</Slider>
@import "@react-aria/example-theme";
.react-aria-Slider {
display: grid;
grid-template-areas: "label output"
"track track";
grid-template-columns: 1fr auto;
max-width: 300px;
color: var(--text-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(--border-color);
}
}
.react-aria-SliderThumb {
width: 1.429rem;
height: 1.429rem;
border-radius: 50%;
background: var(--highlight-background);
border: 2px solid var(--background-color);
forced-color-adjust: none;
&[data-dragging] {
background: var(--highlight-background-pressed);
}
&[data-focus-visible] {
outline: 2px solid 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%;
}
}
}
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.
<input> elements for mobile screen reader support, and HTML form integration. <label> and <output> elements are automatically associated to provide context for assistive technologies.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 {Slider, Label, SliderOutput, SliderTrack, SliderThumb} 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.
A Slider uses the following components, which may also be used standalone or reused in other components.
<ExampleCard url="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label" title="Label" description="A label provides context for an input element."> <Label /> </ExampleCard>
</section>To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured Storybook that you can experiment with, or use as a starting point for your own component library.
<StarterKits component="slider" />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}</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']} />
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>
</>
);
}
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>
</>
);
}
Each SliderThumb supports the name prop for integration with HTML forms.
<Slider defaultValue={50}>
<Label>Opacity</Label>
<SliderOutput />
<SliderTrack>
<SliderThumb name="opacity" />
</SliderTrack>
</Slider>
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>
</>
);
}
By default, slider values are percentages 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} />
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} />
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} />
.react-aria-Slider {
&[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%;
}
}
}
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} />
A slider can be disabled using the isDisabled prop.
<MySlider
label="Cookies to share"
defaultValue={25}
isDisabled />
.react-aria-Slider {
&[data-disabled] {
.react-aria-SliderTrack:before {
background: var(--border-color-disabled);
}
.react-aria-SliderThumb {
background: var(--border-color-disabled);
}
}
}
A <Label> accepts all HTML attributes.
A <SliderOutput> renders the current value of the slider as text.
The <SliderTrack> component is a grouping of one or more <SliderThumb> elements.
The <SliderThumb> component renders an individual thumb within a <SliderTrack>.
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 {
/* ... */
}
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>
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] {
/* ... */
}
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'} />
Render props may also be used as children to alter what elements are rendered based on the current state. For example, you could implement custom formatting for the slider's current value.
<SliderOutput>
{state => `${state.getThumbValueLabel(0)} cookies`}
</SliderOutput>
The states, selectors, and render props for each component used in a Slider are documented below.
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:
A Label can be targeted with the .react-aria-Label CSS selector, or by overriding with a custom className.
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:
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:
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:
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" />
}
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).
<ContextTable components={['Slider']} docs={docs} />
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 SliderContext 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>
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.
<ContextTable components={['Label']} docs={docs} />
This example consumes from LabelContext in an existing styled label component to make it compatible with React Aria Components. The <TypeLink links={docs.links} type={docs.exports.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.
///- begin highlight -///
[props, ref] = useContextProps(props, ref, LabelContext);
///- end highlight -///
// ... 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 provides a <TypeLink links={statelyDocs.links} type={statelyDocs.exports.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 {SliderStateContext, LabelContext, NumberField, Input, useSlottedContext} from 'react-aria-components';
function SliderNumberField() {
/*- begin highlight -*/
let state = React.useContext(SliderStateContext)!;
/*- end highlight -*/
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>
.react-aria-Input {
border-radius: 6px;
width: 3ch;
}
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.