Add `format` prop to RAC Slider to support arbitrary `aria-valuetext`.
Closes #6402
The aria-valuetext attribute specifies the human-readable representation of a numeric value. RAC components that allow users to manipulate numeric values should provide a means of setting aria-valuetext on the appropriate underlying element.
Currently, RAC sliders are implemented using one input element per thumb. The Slider component takes a formatOptions prop; the aria-valuetext of each input is computed as
Intl.NumberFormat(currentLocale, formatOptions).format(value)
(The formatted values of all the thumbs in a slider are also used to set the default content of the SliderOutput component; this default can be overriden by providing children to the output component, whereas the aria-valuetext of each thumb presently can't be.)
The current approach is probably flexible enough for most use cases, but falls somewhat short of the spec, which gives the following example:
Authors SHOULD only set the
aria-valuetextattribute when the rendered value cannot be meaningfully represented as a number. For example, a slider may have rendered values ofsmall,medium, andlarge. In this case, the values ofaria-valuenowcould range from 1 through 3, which indicate the position of each value in the value space, but thearia-valuetextwould be one of the strings:small,medium, orlarge.
Currently, there is no way to implement this in RAC.
This PR adds a format prop to Slider that accepts either a NumberFormatOptions object (the same as the current formatOptions prop) or a function with the signature (value: number, locale: string) => string. If format is an object, then the behavior will be the same as if formatOptions were specified with that object. If format is a function, then that function will be used to set the aria-valuetext for each input, as well as the default content of SliderOutput.
This approach makes only a modest change to the code, and enables many use cases. A few examples:
-
Mapping values to strings, as in the spec's
small,medium, andlargeexample. -
Representing numerical values in ways not supported by
Intl.NumberFormat: time intervals (e.g.2h15m), magnifications (e.g.10x), fractions (e.g.½), etc. -
Providing multiple representations of a single value, such as a temperature represented in both degrees Celsius and Fahrenheit.
-
Implementing non-linear scaling. It is currently almost possible to implement a non-linear scale by wrapping the existing
Slidercomponent, e.g.const [value, setValue] = useState(0); <Slider value={Math.log10(value)} onChange={(value) => setValue(10 ** value)} {...etc} > {/* ... */} <SliderOutput>{value}</SliderOutput> {/* ... */} </Slider>;This is a working logarithmically scaled slider in every respect except the incorrect
aria-valuetexton the underlying input.(There is an existing issue, #1830, addressing non-linear scaling; a design was approved two years ago, but never implemented. This PR provides an alternative solution to the problem.)
I've included a storybook entry for testing the new functionality. I'm happy to add tests and documentation updates, but I wanted to get some feedback on this PR before I committed to that work.
Edit: I noticed after creating this PR that Spectrum sliders already specify a getValueLabel function for customizing a slider's output; as implemented, it does not correctly set aria-valuetext, as can be observed by turning on a screen reader and looking at the "donuts to buy" example in the documentation. If this PR is merged, then Spectrum could be modified to have getValueLabel correctly propagate its computed label to aria-valuetext. I'd also be happy to change the name of the prop in this PR to getValueLabel for consistency with Spectrum, if the maintainers prefer.
✅ Pull Request Checklist:
- [x] Included link to corresponding React Spectrum GitHub Issue.
- [ ] Added/updated unit tests and storybook for this change (for new code or code which already has tests).
- [x] Filled out test instructions.
- [ ] Updated documentation (if it already exists for this component).
- [x] Looked at the Accessibility Practices for this feature - Aria Practices
📝 Test Instructions:
http://localhost:9003/?path=/story/react-aria-components--slider-custom-format&args=showAlternative:!true&providerSwitcher-express=false&strict=true
https://github.com/adobe/react-spectrum/assets/108963010/c341b84e-0dc9-4279-b955-85eb3ff8576d
GET_BUILD
Discussed in the team today, we have a number of opposing opinions and have not settled on the API we'd like for this yet. Thank you for your patience.
Some requirements we have so far:
- It should be easy to "do the right thing"
- Whatever value is in the output element should be the aria-valuetext as well by default
- We don't want to send this through the useNumberFormatter or deprecate formatOptions
- There should only be one way to accomplish SliderOutput formatting/aria-valuetext
Some other options we talked about
- use a getValueLabel like we do in RSP, this leads us to multiple ways to accomplish the same task for what is rendered into "SliderOutput"
- add a prop on the Thumbs, people will likely miss this and we can't make it required nor do we want to
- reuse the function passed to SliderOutput through a registration context pattern, however, we allow ReactNode here and that isn't compatible with aria-valuetext's type of string.
- use
textContentand use a layout effect to grab it and set it on the inputs, might be a render or two behind, would ignore most formatting
Something to keep an eye out for, when making a range such as https://react-spectrum.adobe.com/react-aria/Slider.html#reusable-wrappers but with labels like this getValueLabel example https://react-spectrum.adobe.com/react-spectrum/Slider.html#labeling would the valuetext end up as "20 - 30 donuts" or "20 donuts - 30 donuts"?
I'll be marking this as "on hold" until we can get the API resolved.
Glad to hear it’s being discussed.
Some requirements we have so far: […]
We don't want to send this through the useNumberFormatter or deprecate formatOptions
There should only be one way to accomplish SliderOutput formatting/aria-valuetext
Aren’t these requirements in direct contradiction? If there needs to be only one way to set the label, and you’re committed to keeping the existing formatOptions/NumberFormat mechanism for setting the label, and you want to make it possible to set the label to values other than those that can be handled by the existing mechanism… Maybe I’m missing something, but I can’t see what design could possibly satisfy all of those contraints.
Some other options we talked about [...]
- reuse the function passed to SliderOutput through a
I think part of a sentence got cut off?
Aren’t these requirements in direct contradiction?
Yep, this is most of the source of our conflict