Skip to content

Commit

Permalink
fix(radio): make host radio role to fix a11y
Browse files Browse the repository at this point in the history
Why? role="radio" items need to be in the same DOM scope to properly announce "radio X of X" and not "radio 1 of 1"

PiperOrigin-RevId: 559908996
  • Loading branch information
asyncLiz authored and Copybara-Service committed Aug 24, 2023
1 parent dfc87f3 commit 0711f8c
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 123 deletions.
41 changes: 26 additions & 15 deletions docs/components/radio.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Radios behave like
elements and form a group with the same `name` attribute. Only one radio can be
selected in a group.

Radios can be pre-selected by adding a `selected` attribute.
Radios can be pre-selected by adding a `checked` attribute.

Add a `value` to identify which radio is selected in a form.

Expand All @@ -87,7 +87,7 @@ Add a `value` to identify which radio is selected in a form.
<form>
<md-radio name="animals" value="cats"></md-radio>
<md-radio name="animals" value="dogs"></md-radio>
<md-radio name="animals" value="birds" selected></md-radio>
<md-radio name="animals" value="birds" checked></md-radio>
</form>
```

Expand Down Expand Up @@ -115,31 +115,42 @@ Associate a label with a radio using the `<label>` element.
<!-- catalog-only-end -->

```html
<label>
<md-radio name="animals" value="cats"></md-radio>
Cats
</label>
<md-radio id="cats-radio" name="animals" value="cats"></md-radio>
<label for="cats-radio">Cats</label>

<md-radio id="dogs-radio" name="animals" value="dogs"></md-radio>
<label for="dogs-radio">Dogs</label>
```

> Note: do not wrap radios inside of a `<label>`, which stops screen readers
> from correctly announcing the number of radios in a group.
## Accessibility

Add an
[`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label)<!-- {.external} -->
attribute to radios without labels or radios whose labels need to be more
descriptive.

Place radios inside a
[`role="radiogroup"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/radiogroup_role)<!-- {.external} -->.
Radio groups must display a label, either with `aria-label` or
`aria-labelledby`.

```html
<label>
<md-radio name="group" value="1" aria-label="First"></md-radio>
1st
</label>
<label>
<md-radio name="group" value="2" aria-label="Second"></md-radio>
2nd
</label>
<div role="radiogroup" aria-labelledby="group-title">
<h3 id="group-title">Starting position</h3>
<div>
<md-radio id="first-radio" name="group" value="1"
aria-label="First"></md-radio>
<label for="first-radio">1st</label>
</div>
<div>
<md-radio id="second-radio" name="group" value="2"
aria-label="Second"></md-radio>
<label for="second-radio">2nd</label>
</div>
</div>
```

> Note: radios are not automatically labelled by `<label>` elements and always
Expand Down Expand Up @@ -191,6 +202,6 @@ Token | Default value
</style>

<md-radio name="group"></md-radio>
<md-radio name="group" selected></md-radio>
<md-radio name="group" checked></md-radio>
<md-radio name="group"></md-radio>
```
12 changes: 1 addition & 11 deletions radio/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,12 @@ import './index.js';
import './material-collection.js';

import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo} from './material-collection.js';
import {boolInput, Knob, selectDropdown} from './index.js';
import {boolInput, Knob} from './index.js';

import {stories, StoryKnobs} from './stories.js';

const collection =
new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>('Radio', [
new Knob('checked', {
ui: selectDropdown({
options: [
{value: '0', label: 'First'},
{value: '1', label: 'Second'},
{value: '2', label: 'Third'},
],
}),
defaultValue: '1'
}),
new Knob('disabled', {ui: boolInput(), defaultValue: false}),
]);

Expand Down
128 changes: 70 additions & 58 deletions radio/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,81 +7,93 @@
import '@material/web/radio/radio.js';

import {labelStyles, MaterialStoryInit} from './material-collection.js';
import {html} from 'lit';
import {css, html} from 'lit';

/** Knob types for radio stories. */
export interface StoryKnobs {
checked: '0'|'1'|'2'|undefined;
disabled: boolean;
}

const standard: MaterialStoryInit<StoryKnobs> = {
name: '<md-radio>',
render({checked, disabled}) {
const radio: MaterialStoryInit<StoryKnobs> = {
name: 'Radios',
render({disabled}) {
return html`
<md-radio
name="story1"
value="radioA"
touch-target="wrapper"
.checked=${checked === '0'}
.disabled="${disabled || false}">
</md-radio>
<md-radio
name="story1"
value="radioB"
touch-target="wrapper"
.checked=${checked === '1'}
.disabled="${disabled || false}">
</md-radio>
<md-radio
name="story1"
value="radioC"
touch-target="wrapper"
.checked=${checked === '2'}
.disabled="${disabled || false}">
</md-radio>
`;
},
};

const labeled: MaterialStoryInit<StoryKnobs> = {
name: 'Labeled',
styles: labelStyles,
render({checked, disabled}) {
return html`
<label>
First Radio
<div role="radiogroup" aria-label="An example group of radio buttons">
<md-radio
name="story2"
value="radioA"
aria-label="First radio"
name="group"
touch-target="wrapper"
.checked=${checked === '0'}
.disabled="${disabled || false}">
?disabled=${disabled}>
</md-radio>
</label>
<label>
Second Radio
<md-radio
name="story2"
value="radioB"
aria-label="Second radio"
name="group"
touch-target="wrapper"
.checked=${checked === '1'}
.disabled="${disabled || false}">
?disabled=${disabled}>
</md-radio>
</label>
<label>
Third Radio
<md-radio
name="story2"
value="radioC"
aria-label="Third radio"
name="group"
touch-target="wrapper"
.checked=${checked === '2'}
.disabled="${disabled || false}">
?disabled=${disabled}>
</md-radio>
</label>
`;
</div>
`;
},
};

const withLabels: MaterialStoryInit<StoryKnobs> = {
name: 'With labels',
styles: [
labelStyles, css`
.column {
display: flex;
flex-direction: column;
}
.radio-label {
display: flex;
align-items: center;
}
`
],
render({disabled}) {
return html`
<div class="column" role="radiogroup" aria-label="Animals">
<div class="radio-label">
<md-radio
aria-label="Birds"
id="birds-radio"
name="with-labels"
touch-target="wrapper"
?disabled=${disabled}>
</md-radio>
<label for="birds-radio">Birds</label>
</div>
<div class="radio-label">
<md-radio
aria-label="Cats"
id="cats-radio"
name="with-labels"
touch-target="wrapper"
?disabled=${disabled}>
</md-radio>
<label for="cats-radio">Cats</label>
</div>
<div class="radio-label">
<md-radio
aria-label="Dogs"
id="dogs-radio"
name="with-labels"
touch-target="wrapper"
?disabled=${disabled}>
</md-radio>
<label for="dogs-radio">Dogs</label>
</div>
</div>
`;
},
};

/** Radio stories. */
export const stories = [standard, labeled];
export const stories = [radio, withLabels];
2 changes: 1 addition & 1 deletion radio/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ import {Radio} from './internal/radio.js';
export class RadioHarness extends Harness<Radio> {
override async getInteractiveElement() {
await this.element.updateComplete;
return this.element.renderRoot.querySelector('input') as HTMLInputElement;
return this.element as HTMLElement;
}
}
2 changes: 1 addition & 1 deletion radio/internal/_radio.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ $_md-sys-motion: tokens.md-sys-motion-values();

display: inline-flex;
height: var(--_icon-size);
outline: none;
position: relative;
vertical-align: top; // Fix extra space when placed inside display: block
width: var(--_icon-size);
Expand Down Expand Up @@ -89,7 +90,6 @@ $_md-sys-motion: tokens.md-sys-motion-values();
input {
appearance: none;
height: 48px;
outline: none;
margin: 0;
position: absolute;
width: 48px;
Expand Down

0 comments on commit 0711f8c

Please sign in to comment.