Skip to content

Commit 20314b5

Browse files
authored
feat: getter prop with aria-label will not add aria-labelledby (#1488)
1 parent a4c3714 commit 20314b5

11 files changed

+71
-12
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,12 @@ Optional properties:
747747
from `getInputProps` and a `disabled` prop will be returned (effectively
748748
disabling the input).
749749

750+
- `aria-label`: By default the menu will add an `aria-labelledby` that refers to
751+
the `<label>` rendered with `getLabelProps`. However, if you provide
752+
`aria-label` to give a more specific label that describes the options
753+
available, then `aria-labelledby` will not be provided and screen readers can
754+
use your `aria-label` instead.
755+
750756
#### `getLabelProps`
751757

752758
This method should be applied to the `label` you render. It is useful for

src/__tests__/downshift.aria.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ test('can override the ids', () => {
2424
expect(container.firstChild).toMatchSnapshot()
2525
})
2626

27-
test('if aria-label is provided to the menu then aria-labelledby is not applied to the label', () => {
27+
test('if aria-label is provided to the menu then aria-labelledby is not applied to the menu', () => {
2828
const customLabel = 'custom menu label'
2929
const {menu} = renderDownshift({
3030
menuProps: {'aria-label': customLabel},
@@ -33,7 +33,16 @@ test('if aria-label is provided to the menu then aria-labelledby is not applied
3333
expect(menu).toHaveAttribute('aria-label', customLabel)
3434
})
3535

36-
function renderDownshift({renderFn, props, menuProps} = {}) {
36+
test('if aria-label is provided to the input then aria-labelledby is not applied to the input', () => {
37+
const customLabel = 'custom menu label'
38+
const {input} = renderDownshift({
39+
inputProps: {'aria-label': customLabel},
40+
})
41+
expect(input).not.toHaveAttribute('aria-labelledby')
42+
expect(input).toHaveAttribute('aria-label', customLabel)
43+
})
44+
45+
function renderDownshift({renderFn, props, menuProps, inputProps} = {}) {
3746
function defaultRenderFn({
3847
getInputProps,
3948
getToggleButtonProps,
@@ -46,7 +55,7 @@ function renderDownshift({renderFn, props, menuProps} = {}) {
4655
<label data-testid="label" {...getLabelProps()}>
4756
label
4857
</label>
49-
<input data-testid="input" {...getInputProps()} />
58+
<input data-testid="input" {...getInputProps(inputProps)} />
5059
<button data-testid="button" {...getToggleButtonProps()} />
5160
<ul data-testid="menu" {...getMenuProps(menuProps)}>
5261
<li data-testid="item-0" {...getItemProps({item: 'item', index: 0})}>

src/downshift.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ class Downshift extends Component {
826826
? this.getItemId(highlightedIndex)
827827
: null,
828828
'aria-controls': isOpen ? this.menuId : null,
829-
'aria-labelledby': this.labelId,
829+
'aria-labelledby': rest && rest['aria-label'] ? undefined : this.labelId,
830830
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
831831
// revert back since autocomplete="nope" is ignored on latest Chrome and Opera
832832
autoComplete: 'off',

src/hooks/useCombobox/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,12 @@ Optional properties:
891891
However, if you are just rendering a primitive component like `<div>`, there
892892
is no need to specify this property. It defaults to `ref`.
893893

894+
- `aria-label`: By default the input will add an `aria-labelledby` that refers to
895+
the `<label>` rendered with `getLabelProps`. However, if you provide
896+
`aria-label` to give a more specific label that describes the options
897+
available, then `aria-labelledby` will not be provided and screen readers can
898+
use your `aria-label` instead.
899+
894900
In some cases, you might want to completely bypass the `refKey` check. Then you
895901
can provide the object `{suppressRefError : true}` as the second argument to
896902
`getInput`. **Please use it with extreme care and only if you are absolutely

src/hooks/useCombobox/__tests__/getInputProps.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ describe('getInputProps', () => {
157157
expect(inputProps.onFocus).toBeUndefined()
158158
expect(inputProps.disabled).toBe(true)
159159
})
160+
161+
test("do not assign 'aria-labelledby' if it has aria-label", () => {
162+
const ariaLabel = 'not so fast'
163+
const {result} = renderUseCombobox()
164+
const inputProps = result.current.getInputProps({'aria-label': ariaLabel})
165+
166+
expect(inputProps['aria-labelledby']).toBeUndefined()
167+
expect(inputProps['aria-label']).toBe(ariaLabel)
168+
})
160169
})
161170

162171
describe('user props', () => {

src/hooks/useCombobox/__tests__/getMenuProps.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ describe('getMenuProps', () => {
5252

5353
expect(menuProps.role).toEqual('listbox')
5454
})
55+
56+
test("do not assign 'aria-labelledby' if it has aria-label", () => {
57+
const ariaLabel = 'not so fast'
58+
const {result} = renderUseCombobox()
59+
const menuProps = result.current.getMenuProps({'aria-label': ariaLabel})
60+
61+
expect(menuProps['aria-labelledby']).toBeUndefined()
62+
expect(menuProps['aria-label']).toBe(ariaLabel)
63+
})
5564
})
5665

5766
describe('user props', () => {

src/hooks/useCombobox/index.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ function useCombobox(userProps = {}) {
270270
}),
271271
id: elementIds.menuId,
272272
role: 'listbox',
273-
'aria-labelledby': elementIds.labelId,
273+
'aria-labelledby':
274+
rest && rest['aria-label'] ? undefined : `${elementIds.labelId}`,
274275
onMouseLeave: callAllEventHandlers(onMouseLeave, () => {
275276
dispatch({
276277
type: stateChangeTypes.MenuMouseLeave,
@@ -480,7 +481,8 @@ function useCombobox(userProps = {}) {
480481
'aria-autocomplete': 'list',
481482
'aria-controls': elementIds.menuId,
482483
'aria-expanded': latestState.isOpen,
483-
'aria-labelledby': elementIds.labelId,
484+
'aria-labelledby':
485+
rest && rest['aria-label'] ? undefined : `${elementIds.labelId}`,
484486
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
485487
// revert back since autocomplete="nope" is ignored on latest Chrome and Opera
486488
autoComplete: 'off',

src/hooks/useSelect/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,12 @@ Optional properties:
753753
primitive component like `<div>`, there is no need to specify this property.
754754
It defaults to `ref`.
755755
756+
- `aria-label`: By default the toggle element will add an `aria-labelledby` that
757+
refers to the `<label>` rendered with `getLabelProps`. However, if you provide
758+
`aria-label` to give a more specific label that describes the options
759+
available, then `aria-labelledby` will not be provided and screen readers can
760+
use your `aria-label` instead.
761+
756762
In some cases, you might want to completely bypass the `refKey` check. Then you
757763
can provide the object `{suppressRefError : true}` as the second argument to
758764
`getToggleButtonProps`. **Please use it with extreme care and only if you are

src/hooks/useSelect/__tests__/getMenuProps.test.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ describe('getMenuProps', () => {
5353
expect(menuProps.role).toEqual('listbox')
5454
})
5555

56-
test("assign '-1' to tabindex", () => {
56+
test("do not assign 'aria-labelledby' if it has aria-label", () => {
57+
const ariaLabel = 'not so fast'
5758
const {result} = renderUseSelect()
58-
const menuProps = result.current.getMenuProps()
59+
const menuProps = result.current.getMenuProps({'aria-label': ariaLabel})
5960

60-
expect(menuProps.tabIndex).toEqual(-1)
61+
expect(menuProps['aria-labelledby']).toBeUndefined()
62+
expect(menuProps['aria-label']).toBe(ariaLabel)
6163
})
6264
})
6365

src/hooks/useSelect/__tests__/getToggleButtonProps.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ describe('getToggleButtonProps', () => {
127127

128128
expect(toggleButtonProps['aria-activedescendant']).toEqual('')
129129
})
130+
131+
test("do not assign 'aria-labelledby' if it has aria-label", () => {
132+
const ariaLabel = 'not so fast'
133+
const {result} = renderUseSelect()
134+
const menuProps = result.current.getToggleButtonProps({'aria-label': ariaLabel})
135+
136+
expect(menuProps['aria-labelledby']).toBeUndefined()
137+
expect(menuProps['aria-label']).toBe(ariaLabel)
138+
})
130139
})
131140

132141
describe('user props', () => {

src/hooks/useSelect/index.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,8 @@ function useSelect(userProps = {}) {
345345
}),
346346
id: elementIds.menuId,
347347
role: 'listbox',
348-
'aria-labelledby': elementIds.labelId,
349-
tabIndex: -1,
348+
'aria-labelledby':
349+
rest && rest['aria-label'] ? undefined : `${elementIds.labelId}`,
350350
onMouseLeave: callAllEventHandlers(onMouseLeave, menuHandleMouseLeave),
351351
...rest,
352352
}
@@ -397,7 +397,8 @@ function useSelect(userProps = {}) {
397397
'aria-controls': elementIds.menuId,
398398
'aria-expanded': latest.current.state.isOpen,
399399
'aria-haspopup': 'listbox',
400-
'aria-labelledby': `${elementIds.labelId}`,
400+
'aria-labelledby':
401+
rest && rest['aria-label'] ? undefined : `${elementIds.labelId}`,
401402
id: elementIds.toggleButtonId,
402403
role: 'combobox',
403404
tabIndex: 0,

0 commit comments

Comments
 (0)