Skip to content

Commit f8e2bee

Browse files
authored
Slider accessibility name calculation fixes for Chrome (#1148) (#1389)
1 parent 1feff7d commit f8e2bee

File tree

5 files changed

+34
-14
lines changed

5 files changed

+34
-14
lines changed

packages/@react-aria/color/src/useColorSlider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export function useColorSlider(props: ColorSliderAriaOptions, state: ColorSlider
3737
let {containerProps, trackProps, labelProps} = useSlider(props, state, trackRef);
3838
let {inputProps, thumbProps} = useSliderThumb({
3939
...props,
40+
// Avoid label being repeated on the thumb since it will already be labeled by the group
41+
'aria-label': undefined,
4042
index: 0
4143
}, state);
4244

packages/@react-aria/slider/src/useSliderThumb.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export function useSliderThumb(
4040
isDisabled,
4141
validationState,
4242
trackRef,
43-
inputRef
43+
inputRef,
44+
'aria-label': ariaLabel
4445
} = opts;
4546

4647
let isVertical = opts.orientation === 'vertical';
@@ -49,10 +50,16 @@ export function useSliderThumb(
4950
let {addGlobalListener, removeGlobalListener} = useGlobalListeners();
5051

5152
let labelId = sliderIds.get(state);
53+
let id = getSliderThumbId(state, index);
54+
let thumbId = ariaLabel ? `${id}-thumb` : undefined;
5255
const {labelProps, fieldProps} = useLabel({
5356
...opts,
54-
id: getSliderThumbId(state, index),
55-
'aria-labelledby': `${labelId} ${opts['aria-labelledby'] ?? ''}`.trim()
57+
id,
58+
// Override due to a Chrome bug where aria-labelledby cannot be a self-reference.
59+
// Instead, we put the label on the thumb element and point to it with aria-labelledby.
60+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1159567
61+
'aria-label': undefined,
62+
'aria-labelledby': `${labelId} ${opts['aria-labelledby'] ?? ''} ${ariaLabel ? thumbId : ''}`.trim()
5663
});
5764

5865
const value = state.values[index];
@@ -165,6 +172,8 @@ export function useSliderThumb(
165172
thumbProps: !isDisabled ? mergeProps(
166173
moveProps,
167174
{
175+
id: thumbId,
176+
'aria-label': ariaLabel,
168177
onMouseDown: () => {onDown(null);},
169178
onPointerDown: (e: React.PointerEvent) => {onDown(e.pointerId);},
170179
onTouchStart: (e: React.TouchEvent) => {onDown(e.changedTouches[0].identifier);}

packages/@react-aria/slider/test/useSliderThumb.test.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,15 @@ describe('useSliderThumb', () => {
8888
return {props0, props1, labelProps, containerProps};
8989
}).result;
9090

91-
let {inputProps: inputProps0} = result.current.props0;
92-
let {inputProps: inputProps1} = result.current.props1;
91+
let {inputProps: inputProps0, thumbProps: thumbProps0} = result.current.props0;
92+
let {inputProps: inputProps1, thumbProps: thumbProps1} = result.current.props1;
9393
let labelId = result.current.containerProps.id;
94-
expect(inputProps0).toMatchObject({type: 'range', step: 2, value: 50, min: 10, max: 70, 'aria-label': 'thumb0', 'aria-labelledby': `${labelId} ${inputProps0.id}`});
95-
expect(inputProps1).toMatchObject({type: 'range', step: 2, value: 70, min: 50, max: 200, 'aria-label': 'thumb1', 'aria-labelledby': `${labelId} ${inputProps1.id}`});
94+
expect(thumbProps0.id).toBe(`${inputProps0.id}-thumb`);
95+
expect(thumbProps1.id).toBe(`${inputProps1.id}-thumb`);
96+
expect(thumbProps0['aria-label']).toBe('thumb0');
97+
expect(thumbProps1['aria-label']).toBe('thumb1');
98+
expect(inputProps0).toMatchObject({type: 'range', step: 2, value: 50, min: 10, max: 70, 'aria-labelledby': `${labelId} ${thumbProps0.id}`});
99+
expect(inputProps1).toMatchObject({type: 'range', step: 2, value: 70, min: 50, max: 200, 'aria-labelledby': `${labelId} ${thumbProps1.id}`});
96100
});
97101
});
98102

packages/@react-spectrum/color/test/ColorSlider.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,14 @@ describe('ColorSlider', () => {
5656
expect(slider).toHaveAttribute('step', '1');
5757
});
5858

59-
it('sets aria-label when label is disbaled', () => {
59+
it('sets aria-label when label is disabled', () => {
6060
let {getByRole} = render(<ColorSlider defaultValue={new Color('#000000')} channel="red" label={null} />);
61+
let group = getByRole('group');
6162
let slider = getByRole('slider');
6263

63-
expect(slider).toHaveAttribute('aria-label', 'Red');
64+
expect(group).toHaveAttribute('aria-label', 'Red');
65+
expect(slider.parentElement).not.toHaveAttribute('aria-label', 'Red');
66+
expect(slider).toHaveAttribute('aria-labelledby', `${group.id}`);
6467
});
6568

6669
it('the slider is focusable', () => {

packages/@react-spectrum/slider/test/RangeSlider.test.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ describe('RangeSlider', function () {
3131
});
3232

3333
it('supports label', function () {
34-
let {getAllByRole, getByRole} = render(<RangeSlider label="The Label" />);
34+
let {getAllByRole, getByLabelText, getByRole} = render(<RangeSlider label="The Label" />);
3535

3636
let group = getByRole('group');
3737
let labelId = group.getAttribute('aria-labelledby');
3838
let [leftSlider, rightSlider] = getAllByRole('slider');
39-
expect(leftSlider.getAttribute('aria-label')).toBe('Minimum');
40-
expect(rightSlider.getAttribute('aria-label')).toBe('Maximum');
41-
expect(leftSlider.getAttribute('aria-labelledby')).toBe(`${labelId} ${leftSlider.id}`);
42-
expect(rightSlider.getAttribute('aria-labelledby')).toBe(`${labelId} ${rightSlider.id}`);
39+
let leftThumb = getByLabelText('Minimum');
40+
let rightThumb = getByLabelText('Maximum');
41+
expect(leftThumb).toHaveAttribute('id', `${leftSlider.id}-thumb`);
42+
expect(rightThumb).toHaveAttribute('id', `${rightSlider.id}-thumb`);
43+
expect(leftSlider.getAttribute('aria-labelledby')).toBe(`${labelId} ${leftThumb.id}`);
44+
expect(rightSlider.getAttribute('aria-labelledby')).toBe(`${labelId} ${rightThumb.id}`);
4345

4446
let label = document.getElementById(labelId);
4547
expect(label).toHaveTextContent('The Label');

0 commit comments

Comments
 (0)