diff --git a/changelogs/upcoming/7675.md b/changelogs/upcoming/7675.md
new file mode 100644
index 00000000000..f3baab655fd
--- /dev/null
+++ b/changelogs/upcoming/7675.md
@@ -0,0 +1,3 @@
+**Accessibility**
+
+- Added `aria-valuetext` attributes to `EuiRange`s with tick labels for improved screen reader UX
diff --git a/src-docs/src/views/range/range_example.js b/src-docs/src/views/range/range_example.js
index 7a845151721..c6cd7244a83 100644
--- a/src-docs/src/views/range/range_example.js
+++ b/src-docs/src/views/range/range_example.js
@@ -238,6 +238,13 @@ export const RangeControlExample = {
label. The value must be included in the range of
values (min-max), though the label may be anything you choose.
+
+ Tick labels can improve the accessibility of your range. If your
+ label is a simple string, it will be read out to screen readers
+ alongside the value. You can also use the{' '}
+ accessibleLabel property to provide more explicit
+ screen reader text.
+
{
min={0}
max={84}
ticks={[
- { label: '1 GB', value: 0 },
- { label: '2GB', value: 14 },
- { label: '4GB', value: 28 },
- { label: '8GB', value: 42 },
- { label: '16GB', value: 56 },
- { label: '32GB', value: 70 },
- { label: '64GB', value: 84 },
+ { label: '1 GB', value: 0, accessibleLabel: 'one gigabyte' },
+ { label: '2GB', value: 14, accessibleLabel: 'two gigabytes' },
+ { label: '4GB', value: 28, accessibleLabel: 'four gigabytes' },
+ { label: '8GB', value: 42, accessibleLabel: 'eight gigabytes' },
+ { label: '16GB', value: 56, accessibleLabel: 'sixteen gigabytes' },
+ { label: '32GB', value: 70, accessibleLabel: 'thirty-two gigabytes' },
+ { label: '64GB', value: 84, accessibleLabel: 'sixty-four gigabytes' },
]}
- aria-label="An example of EuiDualRange with no linear intervals"
+ aria-label="An example of EuiRange with no linear intervals"
/>
>
);
diff --git a/src/components/form/range/range.test.tsx b/src/components/form/range/range.test.tsx
index 1597204c515..6fce5140241 100644
--- a/src/components/form/range/range.test.tsx
+++ b/src/components/form/range/range.test.tsx
@@ -231,4 +231,66 @@ describe('EuiRange', () => {
expect(container.firstChild).toMatchSnapshot();
});
});
+
+ describe('input aria-valuetext', () => {
+ it('should exist when the current value has an accessible label', () => {
+ const { getByRole } = render(
+
+ );
+ expect(getByRole('slider')).toHaveAttribute(
+ 'aria-valuetext',
+ '20, (twenty kilobytes)'
+ );
+ });
+
+ it('falls back to string `label`s if `accessibleLabel` does not exist', () => {
+ const { getByRole } = render(
+
+ );
+
+ expect(getByRole('slider')).toHaveAttribute(
+ 'aria-valuetext',
+ '20, (20kb)'
+ );
+ });
+
+ it('should not exist when the current value does not have a matching label', () => {
+ const { getByRole } = render(
+
+ );
+
+ expect(getByRole('slider')).not.toHaveAttribute('aria-valuetext');
+ });
+ });
});
diff --git a/src/components/form/range/range.tsx b/src/components/form/range/range.tsx
index 9ef374c178a..cecc02ee955 100644
--- a/src/components/form/range/range.tsx
+++ b/src/components/form/range/range.tsx
@@ -27,7 +27,7 @@ import { EuiRangeTooltip } from './range_tooltip';
import { EuiRangeTrack } from './range_track';
import { EuiRangeWrapper } from './range_wrapper';
-import type { EuiRangeProps } from './types';
+import type { EuiRangeProps, EuiRangeTick } from './types';
import { euiRangeStyles } from './range.styles';
import { EuiI18n } from '../../i18n';
@@ -116,6 +116,23 @@ export class EuiRangeClass extends Component<
});
};
+ handleAriaValueText = (
+ ticks: EuiRangeTick[],
+ currentVal: string | number
+ ): string | undefined => {
+ const target = ticks.find(
+ (tick) => tick.value.toString() === currentVal.toString()
+ );
+
+ if (target) {
+ return target.accessibleLabel
+ ? `${target.value}, (${target.accessibleLabel})`
+ : typeof target.label === 'string' // Fall back to the label if it's a usable string
+ ? `${target.value}, (${target.label})`
+ : undefined;
+ }
+ };
+
render() {
const { defaultFullWidth } = this.context as FormContextValue;
const {
@@ -220,6 +237,9 @@ export class EuiRangeClass extends Component<
showRange={showRange}
>
;
thumbColor?: EuiRangeLevel['color'];
onResize: EuiResizeObserverProps['onResize'];
+ ariaValueText?: string;
}
export const EuiRangeSlider: FunctionComponent = ({
@@ -69,6 +70,7 @@ export const EuiRangeSlider: FunctionComponent = ({
showRange,
thumbColor,
onResize,
+ ariaValueText,
...rest
}) => {
const classes = classNames('euiRangeSlider', className);
@@ -94,6 +96,7 @@ export const EuiRangeSlider: FunctionComponent = ({
{(resizeRef) => (