Skip to content

Commit

Permalink
fix(list): Update ARIA attributes for radio/checkbox based list (#4055)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Replaced toggleCheckbox adapter method with setCheckedCheckboxOrRadioAtIndex and added 3 more new adapter methods for improved accessibility.
  • Loading branch information
abhiomkar authored Nov 28, 2018
1 parent a6b028d commit 76b404e
Show file tree
Hide file tree
Showing 16 changed files with 1,039 additions and 107 deletions.
186 changes: 160 additions & 26 deletions packages/mdc-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ npm install @material/list
### HTML Structure

```html
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">Single-line item</span>
</li>
<li class="mdc-list-item">
Expand All @@ -61,8 +61,8 @@ in the double line list style as defined by
[the spec](https://material.io/design/components/lists.html#specs) (see "Double line").

```html
<ul class="mdc-list mdc-list--two-line" aria-orientation="vertical">
<li class="mdc-list-item">
<ul class="mdc-list mdc-list--two-line">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">
<span class="mdc-list-item__primary-text">First-line text</span>
<span class="mdc-list-item__secondary-text">Second-line text</span>
Expand Down Expand Up @@ -92,8 +92,8 @@ Multiple related lists can be grouped together using the `mdc-list-group` class
```html
<div class="mdc-list-group">
<h3 class="mdc-list-group__subheader">List 1</h3>
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">line item</span>
</li>
<li class="mdc-list-item">
Expand All @@ -104,7 +104,7 @@ Multiple related lists can be grouped together using the `mdc-list-group` class
</li>
</ul>
<h3 class="mdc-list-group__subheader">List 2</h3>
<ul class="mdc-list" aria-orientation="vertical">
<ul class="mdc-list">
<li class="mdc-list-item">
<span class="mdc-list-item__text">line item</span>
</li>
Expand All @@ -123,8 +123,8 @@ Multiple related lists can be grouped together using the `mdc-list-group` class
MDC List contains an `mdc-list-divider` class which can be used as full-width or inset subdivisions either within lists themselves, or standalone between related groups of content.

```html
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">Item 1 - Division 1</span>
</li>
<li class="mdc-list-item">
Expand All @@ -145,16 +145,16 @@ MDC List contains an `mdc-list-divider` class which can be used as full-width or
OR

```html
<ul class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">Item 1 - List 1</span>
</li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Item 2 - List 1</span>
</li>
</ul>
<hr class="mdc-list-divider">
<ul class="mdc-list" aria-orientation="vertical">
<ul class="mdc-list">
<li class="mdc-list-item">
<span class="mdc-list-item__text">Item 1 - List 2</span>
</li>
Expand All @@ -170,14 +170,14 @@ MDC List can handle selecting/deselecting list elements based on click or keyboa
single list item to become selected and any other previous selected element to become deselected.

```html
<ul id="my-list" class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item" tabindex="0">
<ul id="my-list" class="mdc-list" role="listbox">
<li class="mdc-list-item" role="option" tabindex="0">
<span class="mdc-list-item__text">Single-line item</span>
</li>
<li class="mdc-list-item">
<li class="mdc-list-item" role="option">
<span class="mdc-list-item__text">Single-line item</span>
</li>
<li class="mdc-list-item">
<li class="mdc-list-item" role="option">
<span class="mdc-list-item__text">Single-line item</span>
</li>
</ul>
Expand All @@ -196,14 +196,14 @@ the `mdc-list-item--selected` or `mdc-list-item--activated` class and `aria-sele
creating the list.

```html
<ul id="my-list" class="mdc-list" aria-orientation="vertical">
<li class="mdc-list-item">
<ul id="my-list" class="mdc-list" role="listbox">
<li class="mdc-list-item" role="option" aria-selected="false">
<span class="mdc-list-item__text">Single-line item</span>
</li>
<li class="mdc-list-item mdc-list-item--selected" aria-selected="true" tabindex="0">
<li class="mdc-list-item mdc-list-item--selected" role="option" aria-selected="true" tabindex="0">
<span class="mdc-list-item__text">Single-line item</span>
</li>
<li class="mdc-list-item">
<li class="mdc-list-item" role="option" aria-selected="false">
<span class="mdc-list-item__text">Single-line item</span>
</li>
</ul>
Expand All @@ -215,6 +215,131 @@ var list = new mdc.list.MDCList(listEle);
list.singleSelection = true;
```

### List with radio group

When rendering list radio group with pre-selected radio button the selected list item should contain `aria-checked` set to `true` and the native radio input element contains `checked` attribute, all other list items should have `aria-checked` set to `false`. The list root contains `role="radiogroup"` whereas each list item within radio group contains `role="radio"`.

```html
<ul class="mdc-list" role="radiogroup">
<li class="mdc-list-item" role="radio" aria-checked="false">
<span class="mdc-list-item__graphic">
<div class="mdc-radio">
<input class="mdc-radio__native-control"
type="radio"
id="demo-list-radio-item-1"
name="demo-list-radio-item-group"
value="1">
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-radio-item-1">Option 1</label>
</li>
<li class="mdc-list-item" role="radio" aria-checked="true" tabindex="0">
<span class="mdc-list-item__graphic">
<div class="mdc-radio">
<input class="mdc-radio__native-control"
type="radio"
id="demo-list-radio-item-2"
name="demo-list-radio-item-group"
value="2"
checked>
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-radio-item-2">Option 2</label>
</li>
<li class="mdc-list-item" role="radio" aria-checked="false">
<span class="mdc-list-item__graphic">
<div class="mdc-radio">
<input class="mdc-radio__native-control"
type="radio"
id="demo-list-radio-item-3"
name="demo-list-radio-item-group"
value="3">
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-radio-item-3">Option 3</label>
</li>
</ul>
```

### List with checkbox items

When rendering list with checkbox items all pre-selected list items should contain `aria-checked` set to `true` and the native checkbox input element should contain `checked` attribute, all other list items should have `aria-checked` set to `false`. Each list item in checkbox list contains `role="checkbox"` attribute and the list root should contain `role="group"` and `aria-label` attributes.

```html
<ul class="mdc-list" role="group" aria-label="List with checkbox items">
<li class="mdc-list-item" role="checkbox" aria-checked="false">
<span class="mdc-list-item__graphic">
<div class="mdc-checkbox">
<input type="checkbox"
class="mdc-checkbox__native-control"
id="demo-list-checkbox-item-1" />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark"
viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path"
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-checkbox-item-1">Option 1</label>
</li>
<li class="mdc-list-item" role="checkbox" aria-checked="true" tabindex="0">
<span class="mdc-list-item__graphic">
<div class="mdc-checkbox">
<input type="checkbox"
class="mdc-checkbox__native-control"
id="demo-list-checkbox-item-2"
checked />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark"
viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path"
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-checkbox-item-2">Option 2</label>
</li>
<li class="mdc-list-item" role="checkbox" aria-checked="false">
<span class="mdc-list-item__graphic">
<div class="mdc-checkbox">
<input type="checkbox"
class="mdc-checkbox__native-control"
id="demo-list-checkbox-item-3" />
<div class="mdc-checkbox__background">
<svg class="mdc-checkbox__checkmark"
viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path"
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
</div>
</span>
<label class="mdc-list-item__text" for="demo-list-checkbox-item-3">Option 3</label>
</li>
</ul>
```

## Style Customization

### CSS Classes
Expand Down Expand Up @@ -266,15 +391,21 @@ Mixin | Description

The MDCList JavaScript component implements the WAI-ARIA best practices for
[Listbox](https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox). This includes overriding the default tab behavior
within the list component. You should not add `tabindex` to any of the `li` elements in a list.
within the list component.

The `tabindex` should be set to `0` for first list item element or selected list item element, remaining list item elements should not have `tabindex` set.

Use `role="listbox"` only for single selection list, without this role the `ul` element is implicitely `role="list"`.
Do not use `aria-orientation` attribute for standard list (i.e., `role="list"`), use component's `vertical` property to set the orientation
to vertical.

As the user navigates through the list, any `button` and `a` elements within the list will receive `tabindex="-1"` when
the list item is not focused. When the list item receives focus, the aforementioned elements will receive
`tabIndex="0"`. This allows for the user to tab through list item elements and then tab to the first element after the
list. The `Arrow`, `Home`, and `End` keys should be used for navigating internal list elements. If
list. The `Arrow`, `Home`, and `End` keys should be used for navigating internal list elements. If
`singleSelection=true`, the list will allow the user to use the `Space` or `Enter` keys to select or deselect a list
item. The MDCList will perform the following actions for each key press. Since list interaction will toggle a radio
button or checkbox within the list item, the list will not toggle `tabindex` for those elements.
button or checkbox within the list item, the list will not toggle `tabindex` for those elements.

Key | Action
--- | ---
Expand Down Expand Up @@ -305,7 +436,7 @@ skip over the entire list. If the list items contain sub-elements that are focus
these should also receive `tabIndex="-1"`.

```html
<ul id="my-list" class="mdc-list" aria-orientation="vertical">
<ul id="my-list" class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">Single-line item</span>
<button tabindex="-1"></button>
Expand All @@ -328,7 +459,7 @@ to `-1`. The foundation method `setSelectedIndex()` should be called with the in
after the foundation is instantiated.

```html
<ul id="my-list" class="mdc-list" aria-orientation="vertical">
<ul id="my-list" class="mdc-list">
<li class="mdc-list-item" tabindex="-1">
<span class="mdc-list-item__text">Single-line item</span>
<button tabindex="-1"></button>
Expand All @@ -355,7 +486,10 @@ Method Signature | Description
`focusItemAtIndex(index: Number) => void` | Focuses the list item at the `index` value specified.
`setTabIndexForListItemChildren(index: Number, value: Number) => void` | Sets the `tabindex` attribute to `value` for each child button or anchor element in the list item at the `index` specified.
`followHref(element: Element) => void` | If the given element has an href, follows the link.
`toggleCheckbox(index: number) => boolean` | Toggles a checkbox and radio button in the list item and returns true/false if one was found.
`hasRadioAtIndex(index: number) => boolean` | Returns true if radio button is present at given list item index.
`hasCheckboxAtIndex(index: number) => boolean` | Returns true if checkbox is present at given list item index.
`isCheckboxCheckedAtIndex(index: number) => boolean` | Returns true if checkbox inside a list item is checked.
`setCheckedCheckboxOrRadioAtIndex(index: number, isChecked: boolean) => void` | Sets the checked status of checkbox or radio at given list item index.

### `MDCListFoundation`

Expand Down
24 changes: 21 additions & 3 deletions packages/mdc-list/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,29 @@ class MDCListAdapter {
followHref(ele) {}

/**
* Toggles the checkbox or radio button within a list item.
* @param {number} index
* @return {boolean} true if a radio button or checkbox was present.
* @return {boolean} Returns true if radio button is present at given list item index.
*/
toggleCheckbox(index) {}
hasRadioAtIndex(index) {}

/**
* @param {number} index
* @return {boolean} Returns true if checkbox is present at given list item index.
*/
hasCheckboxAtIndex(index) {}

/**
* @param {number} index
* @return {boolean} Returns true if checkbox inside a list item is checked.
*/
isCheckboxCheckedAtIndex(index) {}

/**
* Sets the checked status of checkbox or radio at given list item index.
* @param {number} index
* @param {boolean} isChecked
*/
setCheckedCheckboxOrRadioAtIndex(index, isChecked) {}
}

export default MDCListAdapter;
4 changes: 4 additions & 0 deletions packages/mdc-list/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const strings = {
ARIA_ORIENTATION: 'aria-orientation',
ARIA_ORIENTATION_HORIZONTAL: 'horizontal',
ARIA_SELECTED: 'aria-selected',
ARIA_CHECKED: 'aria-checked',
ARIA_CHECKED_RADIO_SELECTOR: '[role="radio"][aria-checked="true"]',
RADIO_SELECTOR: 'input[type="radio"]:not(:disabled)',
CHECKBOX_SELECTOR: 'input[type="checkbox"]:not(:disabled)',
CHECKBOX_RADIO_SELECTOR: 'input[type="checkbox"]:not(:disabled), input[type="radio"]:not(:disabled)',
CHILD_ELEMENTS_TO_TOGGLE_TABINDEX: `.${cssClasses.LIST_ITEM_CLASS} button:not(:disabled),
.${cssClasses.LIST_ITEM_CLASS} a`,
Expand Down
Loading

0 comments on commit 76b404e

Please sign in to comment.