Skip to content

Commit

Permalink
Improve aria accessibility (#592)
Browse files Browse the repository at this point in the history
* encode expected `aria-expanded` behaviour

* ensure `aria-expanded` has the correct value

`aria-expanded` can be in 3 different states:

| Value               | Description                                                                |
| ------------------- | -------------------------------------------------------------------------- |
| false               | The grouping element this element owns or controls is collapsed.           |
| true                | The grouping element this element owns or controls is expanded.            |
| undefined (default) | The element does not own or control a grouping element that is expandable. |

Ref: https://www.w3.org/TR/wai-aria-1.2/#aria-expanded

Fixes: #580

* ensure `disabled` prop in Vue is not rendered when `false`

* update changelog
  • Loading branch information
RobinMalfait authored Jun 4, 2021
1 parent 11c461e commit 045b843
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve `disabled` and `tabindex` prop handling ([#512](https://github.com/tailwindlabs/headlessui/pull/512))
- Improve React peer dependency version range ([#544](https://github.com/tailwindlabs/headlessui/pull/544))
- Improve types for the `open` prop in the `Dialog` component ([#550](https://github.com/tailwindlabs/headlessui/pull/550))
- Improve `aria-expanded` logic ([#592](https://github.com/tailwindlabs/headlessui/pull/592))

## [Unreleased - Vue]

Expand All @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Improve `disabled` and `tabindex` prop handling ([#512](https://github.com/tailwindlabs/headlessui/pull/512))
- Improve reactivity when destructuring from props ([#512](https://github.com/tailwindlabs/headlessui/pull/512))
- Improve `aria-expanded` logic ([#592](https://github.com/tailwindlabs/headlessui/pull/592))

## [@headlessui/react@v1.2.0] - 2021-05-10

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
ref: buttonRef,
id: state.buttonId,
type: 'button',
'aria-expanded': state.disclosureState === DisclosureStates.Open ? true : undefined,
'aria-expanded': props.disabled ? undefined : state.disclosureState === DisclosureStates.Open,
'aria-controls': state.linkedPanel ? state.panelId : undefined,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
type: 'button',
'aria-haspopup': true,
'aria-controls': state.optionsRef.current?.id,
'aria-expanded': state.listboxState === ListboxStates.Open ? true : undefined,
'aria-expanded': state.disabled ? undefined : state.listboxState === ListboxStates.Open,
'aria-labelledby': labelledby,
disabled: state.disabled,
onKeyDown: handleKeyDown,
Expand Down
2 changes: 1 addition & 1 deletion packages/@headlessui-react/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
type: 'button',
'aria-haspopup': true,
'aria-controls': state.itemsRef.current?.id,
'aria-expanded': state.menuState === MenuStates.Open ? true : undefined,
'aria-expanded': props.disabled ? undefined : state.menuState === MenuStates.Open,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
onClick: handleClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
ref: buttonRef,
id: state.buttonId,
type: 'button',
'aria-expanded': state.popoverState === PopoverStates.Open ? true : undefined,
'aria-expanded': props.disabled ? undefined : state.popoverState === PopoverStates.Open,
'aria-controls': state.panel ? state.panelId : undefined,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,20 @@ export function assertMenuButton(

case MenuState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case MenuState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -326,12 +334,20 @@ export function assertListboxButton(

case ListboxState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case ListboxState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -628,12 +644,20 @@ export function assertDisclosureButton(

case DisclosureState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case DisclosureState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -752,12 +776,20 @@ export function assertPopoverButton(

case PopoverState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case PopoverState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ export let DisclosureButton = defineComponent({
let propsWeControl = {
id: this.id,
type: 'button',
'aria-expanded': api.disclosureState.value === DisclosureStates.Open ? true : undefined,
'aria-expanded': this.$props.disabled
? undefined
: api.disclosureState.value === DisclosureStates.Open,
'aria-controls': this.ariaControls,
disabled: this.$props.disabled ? true : undefined,
onClick: this.handleClick,
onKeydown: this.handleKeyDown,
onKeyup: this.handleKeyUp,
Expand Down
6 changes: 4 additions & 2 deletions packages/@headlessui-vue/src/components/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,13 @@ export let ListboxButton = defineComponent({
type: 'button',
'aria-haspopup': true,
'aria-controls': dom(api.optionsRef)?.id,
'aria-expanded': api.listboxState.value === ListboxStates.Open ? true : undefined,
'aria-expanded': api.disabled.value
? undefined
: api.listboxState.value === ListboxStates.Open,
'aria-labelledby': api.labelRef.value
? [dom(api.labelRef)?.id, this.id].join(' ')
: undefined,
disabled: api.disabled.value,
disabled: api.disabled.value === true ? true : undefined,
onKeydown: this.handleKeyDown,
onKeyup: this.handleKeyUp,
onClick: this.handleClick,
Expand Down
2 changes: 1 addition & 1 deletion packages/@headlessui-vue/src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export let MenuButton = defineComponent({
type: 'button',
'aria-haspopup': true,
'aria-controls': dom(api.itemsRef)?.id,
'aria-expanded': api.menuState.value === MenuStates.Open ? true : undefined,
'aria-expanded': this.$props.disabled ? undefined : api.menuState.value === MenuStates.Open,
onKeydown: this.handleKeyDown,
onKeyup: this.handleKeyUp,
onClick: this.handleClick,
Expand Down
5 changes: 4 additions & 1 deletion packages/@headlessui-vue/src/components/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ export let PopoverButton = defineComponent({
ref: 'el',
id: api.buttonId,
type: 'button',
'aria-expanded': api.popoverState.value === PopoverStates.Open ? true : undefined,
'aria-expanded': this.$props.disabled
? undefined
: api.popoverState.value === PopoverStates.Open,
'aria-controls': dom(api.panel) ? api.panelId : undefined,
disabled: this.$props.disabled ? true : undefined,
onKeydown: this.handleKeyDown,
onKeyup: this.handleKeyUp,
onClick: this.handleClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,20 @@ export function assertMenuButton(

case MenuState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case MenuState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -326,12 +334,20 @@ export function assertListboxButton(

case ListboxState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case ListboxState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -628,12 +644,20 @@ export function assertDisclosureButton(

case DisclosureState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case DisclosureState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down Expand Up @@ -752,12 +776,20 @@ export function assertPopoverButton(

case PopoverState.InvisibleHidden:
expect(button).toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

case PopoverState.InvisibleUnmounted:
expect(button).not.toHaveAttribute('aria-controls')
expect(button).not.toHaveAttribute('aria-expanded')
if (button.hasAttribute('disabled')) {
expect(button).not.toHaveAttribute('aria-expanded')
} else {
expect(button).toHaveAttribute('aria-expanded', 'false')
}
break

default:
Expand Down

0 comments on commit 045b843

Please sign in to comment.