Skip to content

Commit 6fe63c5

Browse files
KristinLBradleyandgen404MelSumnerheatherlarsen
authored
Alert - don't use role or aria-live for non-valid alert usages (HDS-3919) (#2500)
Co-authored-by: Andrew Gendel <124841193+andgen404@users.noreply.github.com> Co-authored-by: Melanie Sumner <melanie@hashicorp.com> Co-authored-by: Heather Larsen <hlarsen@hashicorp.com>
1 parent 22af449 commit 6fe63c5

File tree

11 files changed

+190
-115
lines changed

11 files changed

+190
-115
lines changed

.changeset/tough-lies-divide.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashicorp/design-system-components": patch
3+
---
4+
5+
`Alert` - Removed role="alert" and aria-live="polite" attributes from Alerts with color set to "neutral" or "highlight"

packages/components/src/components/hds/alert/index.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div
66
class={{this.classNames}}
77
role={{this.role}}
8-
aria-live="polite"
8+
aria-live={{if this.role "polite"}}
99
aria-labelledby={{this.ariaLabelledBy}}
1010
{{did-insert this.didInsert}}
1111
...attributes

packages/components/src/components/hds/alert/index.ts

+14-29
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export interface HdsAlertSignature {
6262
}
6363

6464
export default class HdsAlert extends Component<HdsAlertSignature> {
65-
@tracked role = 'alert';
65+
@tracked role?: string;
6666
@tracked ariaLabelledBy?: string;
6767

6868
constructor(owner: unknown, args: HdsAlertSignature['Args']) {
@@ -76,12 +76,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
7676
);
7777
}
7878

79-
/**
80-
* @param color
81-
* @type {enum}
82-
* @default neutral
83-
* @description Determines the color scheme for the alert.
84-
*/
79+
// Determines the color scheme for the alert.
8580
get color(): HdsAlertColors {
8681
const { color = DEFAULT_COLOR } = this.args;
8782

@@ -95,12 +90,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
9590
return color;
9691
}
9792

98-
/**
99-
* @param icon
100-
* @type {string}
101-
* @default false
102-
* @description The name of the icon to be used.
103-
*/
93+
// The name of the icon to be used.
10494
get icon(): HdsIconSignature['Args']['name'] | false {
10595
const { icon } = this.args;
10696

@@ -127,11 +117,6 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
127117
}
128118
}
129119

130-
/**
131-
* @param onDismiss
132-
* @type {function}
133-
* @default () => {}
134-
*/
135120
// eslint-disable-next-line @typescript-eslint/no-explicit-any
136121
get onDismiss(): ((event: MouseEvent, ...args: any[]) => void) | false {
137122
const { onDismiss } = this.args;
@@ -143,11 +128,7 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
143128
}
144129
}
145130

146-
/**
147-
* @param iconSize
148-
* @type {string}
149-
* @description ensures that the correct icon size is used. Automatically calculated.
150-
*/
131+
// Ensures that the correct icon size is used. Automatically calculated.
151132
get iconSize(): HdsIconSignature['Args']['size'] {
152133
if (this.args.type === 'compact') {
153134
return '16';
@@ -156,11 +137,6 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
156137
}
157138
}
158139

159-
/**
160-
* Get the class names to apply to the component.
161-
* @method Alert#classNames
162-
* @return {string} The "class" attribute to apply to the component.
163-
*/
164140
get classNames(): string {
165141
const classes = ['hds-alert'];
166142

@@ -178,8 +154,17 @@ export default class HdsAlert extends Component<HdsAlertSignature> {
178154
const actions = element.querySelectorAll(
179155
`${CONTENT_ELEMENT_SELECTOR} button, ${CONTENT_ELEMENT_SELECTOR} a`
180156
);
181-
if (actions.length) {
157+
158+
// an Alert which actually alerts users (has role="alert" & aria-live="polite") as opposed to an informational or promo "alert"
159+
const isSemanticAlert: boolean =
160+
this.color === 'warning' ||
161+
this.color === 'critical' ||
162+
this.color === 'success';
163+
164+
if (isSemanticAlert && actions.length) {
182165
this.role = 'alertdialog';
166+
} else if (isSemanticAlert) {
167+
this.role = 'alert';
183168
}
184169

185170
// `alertdialog` must have an accessible name so we use either the

packages/components/src/components/hds/toast/index.hbs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
@color={{@color}}
99
@icon={{@icon}}
1010
@onDismiss={{@onDismiss}}
11+
role="alert"
12+
aria-live="polite"
1113
...attributes
1214
as |A|
1315
>

showcase/tests/integration/components/hds/alert/index-test.js

+116-11
Original file line numberDiff line numberDiff line change
@@ -145,37 +145,142 @@ module('Integration | Component | hds/alert/index', function (hooks) {
145145

146146
// A11Y
147147

148-
test('it should render with the correct semantic tags and aria attributes', async function (assert) {
149-
await render(hbs`<Hds::Alert @type="inline" id="test-alert" />`);
148+
// * Colors for alert usages which notify users: success, warning, critical
149+
150+
test('it should render the component with role="alert" and aria-live="polite" for the "success" color', async function (assert) {
151+
await render(
152+
hbs`<Hds::Alert @type="inline" @color="success" id="test-alert" />`
153+
);
154+
assert.dom('#test-alert').hasAttribute('role', 'alert');
155+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
156+
});
157+
158+
test('it should render the component with role="alert" and aria-live="polite" for the "warning" color', async function (assert) {
159+
await render(
160+
hbs`<Hds::Alert @type="inline" @color="warning" id="test-alert" />`
161+
);
162+
assert.dom('#test-alert').hasAttribute('role', 'alert');
163+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
164+
});
165+
166+
test('it should render the component with role="alert" and aria-live="polite" for the "critical" color', async function (assert) {
167+
await render(
168+
hbs`<Hds::Alert @type="inline" @color="critical" id="test-alert" />`
169+
);
150170
assert.dom('#test-alert').hasAttribute('role', 'alert');
171+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
172+
});
173+
174+
// * Colors for informational & promo usages: neutral, highlight
175+
176+
test('it should not render the component with role="alert" and aria-live="polite" for the "neutral" color', async function (assert) {
177+
await render(
178+
hbs`<Hds::Alert @type="inline" @color="neutral" id="test-alert" />`
179+
);
180+
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alert');
181+
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
182+
});
183+
184+
test('it should not render the component with role="alert" and aria-live="polite" for the "highlight" color', async function (assert) {
185+
await render(
186+
hbs`<Hds::Alert @type="inline" @color="highlight" id="test-alert" />`
187+
);
188+
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alert');
189+
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
151190
});
152191

153-
test('it should render with an `alertdialog` role and auto-generated `aria-labelledby` when title and actions are provided', async function (assert) {
192+
// aria-labelledby
193+
194+
test('it should render with an auto-generated `aria-labelledby` when a title is provided', async function (assert) {
154195
await render(
155-
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Title>This is the title</A.Title><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
196+
hbs`
197+
<Hds::Alert @type="inline" id="test-alert" as |A|>
198+
<A.Title>This is the title</A.Title>
199+
</Hds::Alert>
200+
`
156201
);
157202
let title = this.element.querySelector('#test-alert .hds-alert__title');
158-
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
159203
assert.dom('#test-alert').hasAttribute('aria-labelledby', title.id);
160204
});
161205

162-
test('it should render with an `alertdialog` role and auto-generated `aria-labelledby` when description and actions are provided', async function (assert) {
206+
test('it should render with an auto-generated `aria-labelledby` when description is provided', async function (assert) {
163207
await render(
164-
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Description>This is the title</A.Description><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
208+
hbs`
209+
<Hds::Alert @type="inline" id="test-alert" as |A|>
210+
<A.Description>This is the title</A.Description>
211+
</Hds::Alert>
212+
`
165213
);
166214
let description = this.element.querySelector(
167215
'#test-alert .hds-alert__description'
168216
);
169-
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
170217
assert.dom('#test-alert').hasAttribute('aria-labelledby', description.id);
171218
});
172219

173-
test('it should render with an `alertdialog` role and `aria-labelledby` when title and actions are provided', async function (assert) {
220+
// Alert dialogs
221+
222+
// * Colors for alert usages which notify users: success, warning, critical
223+
224+
test('it should render with with role="alertdialog" and aria-live="polite" for the "success" color when actions are provided', async function (assert) {
225+
await render(
226+
hbs`
227+
<Hds::Alert @type="inline" @color="success" id="test-alert" as |A|>
228+
<A.Button @text="I am a button" @size="small" />
229+
</Hds::Alert>
230+
`
231+
);
232+
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
233+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
234+
});
235+
236+
test('it should render with with role="alertdialog" and aria-live="polite" for the "warning" color when actions are provided', async function (assert) {
174237
await render(
175-
hbs`<Hds::Alert @type="inline" id="test-alert" as |A|><A.Title id="custom-id">This is the title</A.Title><A.Button @text="I am a button" @size="small" /></Hds::Alert>`
238+
hbs`
239+
<Hds::Alert @type="inline" @color="warning" id="test-alert" as |A|>
240+
<A.Button @text="I am a button" @size="small" />
241+
</Hds::Alert>
242+
`
176243
);
177244
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
178-
assert.dom('#test-alert').hasAttribute('aria-labelledby', 'custom-id');
245+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
246+
});
247+
248+
test('it should render with with role="alertdialog" and aria-live="polite" for the "critical" color when actions are provided', async function (assert) {
249+
await render(
250+
hbs`
251+
<Hds::Alert @type="inline" @color="critical" id="test-alert" as |A|>
252+
<A.Button @text="I am a button" @size="small" />
253+
</Hds::Alert>
254+
`
255+
);
256+
assert.dom('#test-alert').hasAttribute('role', 'alertdialog');
257+
assert.dom('#test-alert').hasAttribute('aria-live', 'polite');
258+
});
259+
260+
// * Colors for informational & promo usages: neutral, highlight
261+
262+
test('it should not render with with role="alertdialog" and aria-live="polite" for the "neutral" color when actions are provided', async function (assert) {
263+
await render(
264+
hbs`
265+
<Hds::Alert @type="inline" @color="neutral" id="test-alert" as |A|>
266+
<A.Button @text="I am a button" @size="small" />
267+
</Hds::Alert>
268+
`
269+
);
270+
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alertdialog');
271+
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
272+
});
273+
274+
test('it should not render with with role="alertdialog" and aria-live="polite" for the "highlight" color when actions are provided', async function (assert) {
275+
await render(
276+
hbs`
277+
<Hds::Alert @type="inline" @color="highlight" id="test-alert" as |A|>
278+
<A.Button @text="I am a button" @size="small" />
279+
</Hds::Alert>
280+
`
281+
);
282+
assert.dom('#test-alert').doesNotHaveAttribute('role', 'alertdialog');
283+
assert.dom('#test-alert').doesNotHaveAttribute('aria-live', 'polite');
179284
});
180285

181286
// ASSERTIONS

showcase/tests/integration/components/hds/toast/index-test.js

+6
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,10 @@ module('Integration | Component | hds/toast/index', function (hooks) {
1717
await render(hbs`<Hds::Toast id="test-toast" />`);
1818
assert.dom('#test-toast').hasClass('hds-toast');
1919
});
20+
21+
test('it should render the component with "role"="alert" and aria-live="polite" by default', async function (assert) {
22+
await render(hbs`<Hds::Toast id="test-toast" />`);
23+
assert.dom('#test-toast').hasAttribute('role', 'alert');
24+
assert.dom('#test-toast').hasAttribute('aria-live', 'polite');
25+
});
2026
});

website/docs/components/alert/partials/code/component-api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
Sets the type of alert.
2323
</C.Property>
2424
<C.Property @name="color" @type="enum" @values={{array "neutral" "highlight" "success" "warning" "critical"}} @default="neutral">
25-
Sets the color scheme for `background`, `border`, `title`, and `description`, which **cannot** be overridden.<br/><br/>`color` results in a default `icon`, which **can** be overridden.
25+
Sets the color scheme for `background`, `border`, `title`, and `description`, which **cannot** be overridden.<br/><br/>`color` results in a default `icon`, which **can** be overridden.<br/><br/>For the “success”, “warning”, and “critical” colors, either `role="alert"` or `role="alertdialog"` and `aria-live="polite"` will be included by default which can be overridden if necessary.<br/><br/>The “neutral” and “highlight” colors do not include a role attribute or `aria-live="polite"` by default.
2626
</C.Property>
2727
<C.Property @name="icon" @type="string | false">
2828
Override the default `icon` name, which is determined by the `color` argument.<br/><br/>accepts any [icon](/icons/library) name, or `false`, for no icon.

website/docs/components/alert/partials/code/how-to-use.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,14 @@ The default `@tag` is `"div"` because the correct value is dependent on the indi
6868

6969
### Color
7070

71-
A different color can be applied to the Alert using the `color` argument. This will determine the default icon used in the Alert, unless overwritten.
71+
The available color values are `neutral` (the default), `highlight``success``warning`, and `critical`. Setting a color value will also determine the default icon used in the Alert, although it is customizable.
7272

73+
The color value will also determine some accessibility features of the component, so this should be taken into consideration when choosing which Alert `color` value to use.
74+
75+
76+
If the alert is being used in an informational or promotional way, `neutral` or `highlight` colors should be chosen.
77+
78+
The other color values map to accessibility-related roles, and will ensure that essential information is presented to the user with assistive technology in a timely manner.
7379
```handlebars
7480
<Hds::Alert @type="inline" @color="success" as |A|>
7581
<A.Title>Title here</A.Title>

0 commit comments

Comments
 (0)