Skip to content

Commit ee4d624

Browse files
authored
fix(clear-button): updates documentation, examples and adds tests for clear button accessibility (#5861)
* fix(clear-button): updates documentation, examples and adds tests for clear button accessibility * fix(clear-button): add deprecation warning * fix(clear-button): update clear button docs to use clear button
1 parent 1d76b70 commit ee4d624

File tree

5 files changed

+175
-42
lines changed

5 files changed

+175
-42
lines changed

1st-gen/packages/button/clear-button.md

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,21 @@ import { ClearButton } from '@spectrum-web-components/button';
2525

2626
### Anatomy
2727

28-
The clear button is a button with only close icon.
28+
The clear button is a button with only a close icon.
2929

3030
```html
31-
<sp-clear-button>Reset</sp-clear-button>
31+
<sp-clear-button label="Reset"></sp-clear-button>
3232
```
3333

3434
#### Label
3535

36-
The label for an `<sp-clear-button>` element can be set via it's default slot or with the `label` attribute. With either method, the label will not be visible but still read by screen readers.
37-
38-
<sp-tabs selected="attribute" auto label="Labelling a button">
39-
<sp-tab value="slot">Default slot</sp-tab>
40-
<sp-tab-panel value="slot">
41-
42-
```html demo
43-
<sp-clear-button>Clear</sp-clear-button>
44-
```
45-
46-
</sp-tab-panel>
47-
<sp-tab value="attribute">Label attribute</sp-tab>
48-
<sp-tab-panel value="attribute">
36+
An accessible label for an `<sp-clear-button>` must be provided using the `label` attribute. This sets the `aria-label` for screen readers. Unlike other button types, the clear button only displays an icon and does not render slot content, so the `label` attribute is the only way to provide an accessible name.
4937

5038
```html demo
51-
<sp-clear-button label="Clear">Clear</sp-clear-button>
39+
<sp-clear-button label="Clear"></sp-clear-button>
5240
```
5341

54-
</sp-tab-panel>
55-
</sp-tabs>
42+
The `label` attribute is required and will be set as the `aria-label` on the element.
5643

5744
### Options
5845

@@ -63,31 +50,31 @@ The label for an `<sp-clear-button>` element can be set via it's default slot or
6350
<sp-tab-panel value="s">
6451

6552
```html demo
66-
<sp-clear-button size="s">Small</sp-clear-button>
53+
<sp-clear-button size="s" label="Clear"></sp-clear-button>
6754
```
6855

6956
</sp-tab-panel>
7057
<sp-tab value="m">Medium</sp-tab>
7158
<sp-tab-panel value="m">
7259

7360
```html demo
74-
<sp-clear-button size="m">Medium</sp-clear-button>
61+
<sp-clear-button size="m" label="Clear"></sp-clear-button>
7562
```
7663

7764
</sp-tab-panel>
7865
<sp-tab value="l">Large</sp-tab>
7966
<sp-tab-panel value="l">
8067

8168
```html demo
82-
<sp-clear-button size="l">Large</sp-clear-button>
69+
<sp-clear-button size="l" label="Clear"></sp-clear-button>
8370
```
8471

8572
</sp-tab-panel>
8673
<sp-tab value="xl">Extra Large</sp-tab>
8774
<sp-tab-panel value="xl">
8875

8976
```html demo
90-
<sp-clear-button size="xl">Extra Large</sp-clear-button>
77+
<sp-clear-button size="xl" label="Clear"></sp-clear-button>
9178
```
9279

9380
</sp-tab-panel>
@@ -119,8 +106,8 @@ While disabled, the `<sp-clear-button>` element will not respond to click events
119106

120107
```html
121108
<sp-button-group>
122-
<sp-clear-button>Normal</sp-clear-button>
123-
<sp-clear-button disabled>Disabled</sp-clear-button>
109+
<sp-clear-button label="Clear"></sp-clear-button>
110+
<sp-clear-button label="Clear" disabled></sp-clear-button>
124111
</sp-button-group>
125112
```
126113

@@ -132,9 +119,10 @@ Events handlers for clicks and other user actions can be registered on a
132119
`<sp-clear-button>` as one would on a standard HTML `<button>` element.
133120

134121
```html
135-
<sp-clear-button onclick="spAlert(this, '<sp-clear-button> clicked!')">
136-
Click me
137-
</sp-clear-button>
122+
<sp-clear-button
123+
label="Click me"
124+
onclick="spAlert(this, '<sp-clear-button> clicked!')"
125+
></sp-clear-button>
138126
```
139127

140128
#### Autofocus
@@ -148,7 +136,7 @@ popover or dialog opens.
148136
<sp-overlay trigger="trigger@click" placement="bottom">
149137
<sp-popover>
150138
<!-- Button will autofocus when open -->
151-
<sp-clear-button autofocus>Clear</sp-clear-button>
139+
<sp-clear-button label="Clear" autofocus></sp-clear-button>
152140
</sp-popover>
153141
</sp-overlay>
154142
```
@@ -157,4 +145,4 @@ popover or dialog opens.
157145

158146
#### Include a label
159147

160-
A button is required to have either text in the default slot or a `label` attribute on the `<sp-clear-button>`.
148+
A button is required to have a `label` attribute on the `<sp-clear-button>` to provide an accessible name for screen readers. The `label` attribute sets the `aria-label` property, ensuring the button is properly announced to assistive technologies.

1st-gen/packages/button/src/ClearButton.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const crossIcon: Record<string, () => TemplateResult> = {
5555
/**
5656
* @element sp-clear-button
5757
*
58-
* @slot - text label of the Clear Button
58+
* @attr {string} label - Required accessible label set as aria-label
5959
*/
6060
export class ClearButton extends SizedMixin(StyledButton, {
6161
noDefaultSize: true,
@@ -64,6 +64,14 @@ export class ClearButton extends SizedMixin(StyledButton, {
6464
return [...super.styles, buttonStyles, crossMediumStyles];
6565
}
6666

67+
/**
68+
* An accessible label that describes the component.
69+
* It will be applied to aria-label, but not visually rendered.
70+
* This attribute is required for clear buttons.
71+
*/
72+
@property()
73+
public override label!: string;
74+
6775
@property({ type: Boolean, reflect: true })
6876
public quiet = false;
6977

@@ -121,4 +129,28 @@ export class ClearButton extends SizedMixin(StyledButton, {
121129
<div class="fill">${super.render()}</div>
122130
`;
123131
}
132+
133+
public override connectedCallback(): void {
134+
super.connectedCallback();
135+
136+
// Deprecation warning for default slot when content is provided
137+
if (window.__swc.DEBUG && this.textContent?.trim()) {
138+
window.__swc.warn(
139+
this,
140+
`The default slot for text content in <${this.localName}> has been deprecated and will be removed in a future release. The clear button is icon-only and does not render slot content. Use the "label" attribute instead to provide an accessible name.`,
141+
'https://opensource.adobe.com/spectrum-web-components/components/button/#clear-button',
142+
{ level: 'deprecation' }
143+
);
144+
}
145+
146+
// Warning for missing label attribute
147+
if (window.__swc.DEBUG && !this.label) {
148+
window.__swc.warn(
149+
this,
150+
`The "label" attribute is required on <${this.localName}> to provide an accessible name for screen readers. Please add a label attribute, e.g., <${this.localName} label="Clear">.`,
151+
'https://opensource.adobe.com/spectrum-web-components/components/button/#clear-button',
152+
{ level: 'high' }
153+
);
154+
}
155+
}
124156
}

1st-gen/packages/button/stories/template.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
ButtonVariants,
1717
} from '@spectrum-web-components/button/src/Button.js';
1818

19+
import '@spectrum-web-components/button/sp-clear-button.js';
20+
import '@spectrum-web-components/button/sp-close-button.js';
21+
1922
export interface Properties {
2023
staticColor?: 'white' | 'black';
2124
variant?: ButtonVariants;
@@ -30,6 +33,7 @@ export interface Properties {
3033
noWrap?: boolean;
3134
iconOnly?: boolean;
3235
label?: string;
36+
componentName?: string;
3337
}
3438

3539
export const Template = ({
@@ -38,14 +42,47 @@ export const Template = ({
3842
size,
3943
treatment,
4044
variant,
41-
}: Properties): TemplateResult => html`
42-
<sp-button
43-
?disabled=${disabled}
44-
?pending=${pending}
45-
size=${ifDefined(size)}
46-
treatment=${ifDefined(treatment)}
47-
variant=${ifDefined(variant)}
48-
>
49-
Test Button
50-
</sp-button>
51-
`;
45+
label = 'Clear',
46+
quiet,
47+
staticColor,
48+
componentName,
49+
}: Properties): TemplateResult => {
50+
// Render clear-button for clear-button docs
51+
if (componentName === 'clear-button') {
52+
return html`
53+
<sp-clear-button
54+
label=${label}
55+
?disabled=${!!disabled}
56+
?quiet=${!!quiet}
57+
size=${ifDefined(size)}
58+
static-color=${ifDefined(staticColor)}
59+
></sp-clear-button>
60+
`;
61+
}
62+
63+
// Render close-button for close-button docs
64+
if (componentName === 'close-button') {
65+
return html`
66+
<sp-close-button
67+
label=${label}
68+
?disabled=${!!disabled}
69+
?quiet=${!!quiet}
70+
size=${ifDefined(size)}
71+
static-color=${ifDefined(staticColor)}
72+
></sp-close-button>
73+
`;
74+
}
75+
76+
// Default: render standard button
77+
return html`
78+
<sp-button
79+
?disabled=${disabled}
80+
?pending=${pending}
81+
size=${ifDefined(size)}
82+
treatment=${ifDefined(treatment)}
83+
variant=${ifDefined(variant)}
84+
>
85+
Test Button
86+
</sp-button>
87+
`;
88+
};

1st-gen/packages/button/test/clear-button.test.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { ElementSize } from '@spectrum-web-components/base';
1515
import { ClearButton } from '@spectrum-web-components/button';
1616
import '@spectrum-web-components/button/sp-clear-button.js';
1717
import { SinonStub, stub } from 'sinon';
18-
import { testForLitDevWarnings } from '../../../test/testing-helpers';
18+
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
1919

2020
describe('Clear Button', () => {
2121
testForLitDevWarnings(async () =>
@@ -33,6 +33,40 @@ describe('Clear Button', () => {
3333
});
3434
});
3535

36+
it('has accessible name when label attribute is provided', async () => {
37+
const el = await fixture<ClearButton>(html`
38+
<sp-clear-button label="Clear field"></sp-clear-button>
39+
`);
40+
41+
await elementUpdated(el);
42+
expect(el.getAttribute('aria-label')).to.equal('Clear field');
43+
await expect(el).to.be.accessible();
44+
});
45+
46+
it('sets aria-label from label property', async () => {
47+
const el = await fixture<ClearButton>(html`
48+
<sp-clear-button></sp-clear-button>
49+
`);
50+
51+
await elementUpdated(el);
52+
expect(el.hasAttribute('aria-label')).to.be.false;
53+
54+
el.label = 'Remove item';
55+
await elementUpdated(el);
56+
expect(el.getAttribute('aria-label')).to.equal('Remove item');
57+
});
58+
59+
it('maintains accessible name in disabled state', async () => {
60+
const el = await fixture<ClearButton>(html`
61+
<sp-clear-button label="Clear" disabled></sp-clear-button>
62+
`);
63+
64+
await elementUpdated(el);
65+
expect(el.getAttribute('aria-label')).to.equal('Clear');
66+
expect(el.hasAttribute('aria-disabled')).to.be.true;
67+
await expect(el).to.be.accessible();
68+
});
69+
3670
describe('dev mode', () => {
3771
let consoleStub: SinonStub;
3872
beforeEach(() => {
@@ -78,5 +112,45 @@ describe('Clear Button', () => {
78112
expect(consoleStub).to.be.calledOnce;
79113
expect(warning.includes(expectedContent)).to.be.true;
80114
});
115+
116+
it('should log deprecation warning when slot content is provided', async () => {
117+
const el = await fixture<ClearButton>(html`
118+
<sp-clear-button label="Clear">Clear</sp-clear-button>
119+
`);
120+
121+
await elementUpdated(el);
122+
123+
const warning = consoleStub.getCall(0).args.at(0);
124+
const expectedContent =
125+
'The default slot for text content in <sp-clear-button> has been deprecated';
126+
127+
expect(consoleStub).to.be.calledOnce;
128+
expect(warning.includes(expectedContent)).to.be.true;
129+
});
130+
131+
it('should log warning when label attribute is missing', async () => {
132+
const el = await fixture<ClearButton>(html`
133+
<sp-clear-button></sp-clear-button>
134+
`);
135+
136+
await elementUpdated(el);
137+
138+
const warning = consoleStub.getCall(0).args.at(0);
139+
const expectedContent =
140+
'The "label" attribute is required on <sp-clear-button>';
141+
142+
expect(consoleStub).to.be.calledOnce;
143+
expect(warning.includes(expectedContent)).to.be.true;
144+
});
145+
146+
it('should not log warning when label attribute is provided without slot content', async () => {
147+
const el = await fixture<ClearButton>(html`
148+
<sp-clear-button label="Clear"></sp-clear-button>
149+
`);
150+
151+
await elementUpdated(el);
152+
153+
expect(consoleStub).to.not.be.called;
154+
});
81155
});
82156
});

1st-gen/projects/documentation/content/_includes/partials/demo.njk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
return args;
123123
}
124124
const renderDemo = (args = {}) => {
125+
// Pass componentName to Template for variant detection
126+
args.componentName = '{{ componentName }}';
125127
render(Template(args), demo);
126128
};
127129
if (config) {

0 commit comments

Comments
 (0)