Skip to content

Commit a1ade7a

Browse files
Require icon accessibility attributes (#679)
* feat(icon): throw error for inaccessible icons Previously, the a11y-title and a11y-hiddden attributes were added and the description attribute was deprecated. This change removes the description attribute and throws an error if an icon is used without proper accessibility attributes. * fix: remove deprecated attribute from tests There was a default description of "" for icons but now that has been removed. * fix(dropdown-menu-nav): add a11y-hidden to icon Because there will always be a category in the button text and the icon is purely decorative, it should be hidden from screen readers. * hotfix(icon): fix typo in test description Co-authored-by: Mat Harris <mat.harris@ithaka.org> * chore: add changeset --------- Co-authored-by: Mat Harris <mat.harris@ithaka.org>
1 parent 60f2a09 commit a1ade7a

File tree

6 files changed

+25
-53
lines changed

6 files changed

+25
-53
lines changed

.changeset/thirty-carrots-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ithaka/pharos': patch
3+
---
4+
5+
Require all icons to have a label or be explicitly hidden

packages/pharos/src/components/alert/pharos-alert.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ describe('pharos-alert', () => {
3939
class="alert__icon"
4040
data-pharos-component="PharosIcon"
4141
a11y-hidden="true"
42-
description=""
4342
name="info-inverse"
4443
>
4544
</pharos-icon>

packages/pharos/src/components/dropdown-menu-nav/pharos-dropdown-menu-nav-category.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class PharosDropdownMenuNavCategory extends ScopedRegistryMixin(FocusMixi
5252
})}"
5353
>
5454
<slot name="category"></slot>
55-
<pharos-icon name="chevron-down"></pharos-icon>
55+
<pharos-icon name="chevron-down" a11y-hidden="true"></pharos-icon>
5656
</button>`;
5757
}
5858
}

packages/pharos/src/components/icon/pharos-icon.test.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ describe('pharos-icon', () => {
77
let component: PharosIcon;
88

99
beforeEach(async () => {
10-
component = await fixture(html`<test-pharos-icon name="base"></test-pharos-icon>`);
10+
component = await fixture(
11+
html`<test-pharos-icon name="base" a11y-title="base-icon"></test-pharos-icon>`
12+
);
1113
});
1214

1315
it('is accessible', async () => {
1416
await expect(component).to.be.accessible();
1517
});
1618

1719
it('throws an error for an invalid icon name', async () => {
18-
component = await fixture(html` <test-pharos-icon name="fake"></test-pharos-icon> `).catch(
19-
(e) => e
20-
);
20+
component = await fixture(
21+
html` <test-pharos-icon name="fake" a11y-title="fake-icon"></test-pharos-icon> `
22+
).catch((e) => e);
2123
expect('Could not get icon named "fake"').to.be.thrown;
2224
});
2325

@@ -46,26 +48,6 @@ describe('pharos-icon', () => {
4648
expect(svg?.getAttribute('aria-hidden')).to.equal('true');
4749
});
4850

49-
it('adds aria-hidden when a11y-hidden is not provided and there is no title or description', async () => {
50-
await component.updateComplete;
51-
const svg = component.renderRoot.querySelector('svg');
52-
expect(svg?.getAttribute('aria-hidden')).to.equal('true');
53-
});
54-
55-
it('does not add aria-hidden if there is a title', async () => {
56-
component.a11yTitle = 'some-label';
57-
await component.updateComplete;
58-
const svg = component.renderRoot.querySelector('svg');
59-
expect(svg?.getAttribute('aria-hidden')).not.to.exist;
60-
});
61-
62-
it('does not add aria-hidden if there is a description', async () => {
63-
component.description = 'some-label';
64-
await component.updateComplete;
65-
const svg = component.renderRoot.querySelector('svg');
66-
expect(svg?.getAttribute('aria-hidden')).not.to.exist;
67-
});
68-
6951
it('sets the svg title properly when a11y-title is set', async () => {
7052
const labelText = 'This is a test title';
7153
component.a11yTitle = labelText;
@@ -74,11 +56,12 @@ describe('pharos-icon', () => {
7456
expect(title).to.contain.text(labelText);
7557
});
7658

77-
it('sets the svg title properly when description is set', async () => {
78-
const labelText = 'This is a test title';
79-
component.description = labelText;
80-
await component.updateComplete;
81-
const title = component.renderRoot.querySelector('svg>title');
82-
expect(title).to.contain.text(labelText);
59+
it('throws an error when neither a11y-title or a11y-hidden are set', async () => {
60+
component = await fixture(html` <test-pharos-icon name="base"></test-pharos-icon> `).catch(
61+
(e) => e
62+
);
63+
expect(
64+
'All icons must have an accessible title (a11y-title) or be marked as hidden to assistive technology (a11y-hidden).'
65+
).to.be.thrown;
8366
});
8467
});

packages/pharos/src/components/icon/pharos-icon.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ export class PharosIcon extends PharosElement {
2727
@property({ type: String, reflect: true })
2828
public name?: IconName;
2929

30-
/**
31-
* A description of what the icon represents
32-
* @attr description
33-
* @deprecated Please use a11yTitle instead.
34-
*/
35-
@property({ type: String, reflect: true })
36-
public description = '';
37-
3830
/**
3931
* Indicates the title to apply as the accessible name of the icon.
4032
* @attr a11y-title
@@ -55,9 +47,10 @@ export class PharosIcon extends PharosElement {
5547

5648
protected override update(changedProperties: PropertyValues): void {
5749
super.update && super.update(changedProperties);
58-
if (this.description.length) {
59-
console.warn(
60-
"The 'description' attribute of pharos-icon is deprecated and will be removed in the next major release. Please use a11y-title or mark the icon as decorative by using a11y-hidden instead."
50+
51+
if (!this.a11yHidden && !this.a11yTitle) {
52+
throw new Error(
53+
`All icons must have an accessible title (a11y-title) or be marked as hidden to assistive technology (a11y-hidden).`
6154
);
6255
}
6356
}
@@ -84,25 +77,19 @@ export class PharosIcon extends PharosElement {
8477

8578
protected override render(): TemplateResult {
8679
const size = this._getIconSize();
87-
const accessibilityLabel = this.a11yTitle || this.description;
88-
let hideIcon = this.a11yHidden;
89-
// Check accessibilityLabel length for backwards compatibility until description is removed
90-
if (hideIcon !== 'true' && accessibilityLabel.length === 0) {
91-
hideIcon = 'true';
92-
}
9380
return html`
9481
<svg
9582
xmlns="http://www.w3.org/2000/svg"
9683
version="1.1"
9784
viewBox="0 0 ${size} ${size}"
9885
class="icon"
9986
role="img"
100-
aria-hidden=${hideIcon || nothing}
87+
aria-hidden=${this.a11yHidden || nothing}
10188
height="${size}"
10289
width="${size}"
10390
focusable="false"
10491
>
105-
<title>${accessibilityLabel}</title>
92+
<title>${this.a11yTitle}</title>
10693
${unsafeSVG(this._svg)}
10794
</svg>
10895
`;

packages/pharos/src/components/text-input/pharos-text-input.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ describe('pharos-text-input', () => {
199199
class="input__icon"
200200
data-pharos-component="PharosIcon"
201201
a11y-hidden="true"
202-
description=""
203202
name="exclamation"
204203
>
205204
</pharos-icon>
@@ -234,7 +233,6 @@ describe('pharos-text-input', () => {
234233
class="input__icon"
235234
data-pharos-component="PharosIcon"
236235
a11y-hidden="true"
237-
description=""
238236
name="checkmark"
239237
>
240238
</pharos-icon>

0 commit comments

Comments
 (0)