Skip to content

Commit e4dc9d0

Browse files
authored
[fix]: remove aria-hidden on labels in field to support text zoom removed field and text-input & text-area (#35308)
1 parent 0f1c945 commit e4dc9d0

File tree

3 files changed

+78
-21
lines changed

3 files changed

+78
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "remove aria-hidden from field labels to zoom text accessibility works, remove field text-input and text-area examples from storybook",
4+
"packageName": "@fluentui/web-components",
5+
"email": "jes@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/web-components/src/field/field.base.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ export class BaseField extends FASTElement {
207207
if (label instanceof HTMLLabelElement) {
208208
label.htmlFor = label.htmlFor || this.input.id;
209209
label.id = label.id || `${this.input.id}--label`;
210-
label.setAttribute('aria-hidden', 'true');
211210
this.input.setAttribute('aria-labelledby', label.id);
212211
}
213212
});

packages/web-components/src/field/field.stories.ts

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { html, repeat } from '@microsoft/fast-element';
2-
import { uniqueId } from '@microsoft/fast-web-utilities';
1+
import { html, ref, repeat } from '@microsoft/fast-element';
32
import { type Meta, renderComponent, type StoryArgs, type StoryObj } from '../helpers.stories.js';
43
import { colorStatusSuccessBackground3 } from '../theme/design-tokens.js';
54
import type { Field as FluentField } from './field.js';
@@ -20,10 +19,35 @@ export const storyTemplate = html<StoryArgs<FluentField>>`
2019
</fluent-field>
2120
`;
2221

22+
const textInputLink = '<a href="/docs/components-textinput--docs">Text Input</a>';
23+
const textAreaLink = '<a href="/docs/components-textarea--docs">Text Area</a>';
24+
25+
const storyDescription = `
26+
The ${textInputLink} and ${textAreaLink} components have a specific implementation with \`<fluent-field>\` that differs from other form controls to ensure proper accessibility support.
27+
28+
**For Text Input and Text Area:**
29+
- The label must be passed as a child element (not using the field's label slot)
30+
- This implementation is required for accessibility features like zoom text and voice over to work correctly
31+
- A previous issue where the aria-hidden attribute prevented zoom text functionality on the field label has been resolved
32+
33+
**For Other Form Controls (checkbox, radio, etc.):**
34+
- The label should be placed in the field's label slot using \`slot="label"\`
35+
- This is the standard implementation pattern for most form controls
36+
37+
This distinction ensures that text-based inputs maintain proper accessibility while other controls follow the standard slotting pattern.
38+
`;
39+
2340
export default {
2441
title: 'Components/Field',
2542
render: renderComponent(storyTemplate),
2643
excludeStories: ['storyTemplate'],
44+
parameters: {
45+
docs: {
46+
description: {
47+
component: storyDescription,
48+
},
49+
},
50+
},
2751
args: {
2852
label: {
2953
text: 'Example field',
@@ -33,7 +57,7 @@ export default {
3357
icon: () => html`${SuccessIcon}`,
3458
},
3559
labelSlottedContent: () => html`<label slot="label">${story => story.label.text}</label>`,
36-
inputSlottedContent: () => html`<fluent-text-input slot="input"></fluent-text-input>`,
60+
inputSlottedContent: () => html`<fluent-checkbox slot="input"></fluent-checkbox>`,
3761
labelPosition: LabelPosition.above,
3862
},
3963
argTypes: {
@@ -81,7 +105,7 @@ export const LabelPositions: Story = {
81105
<div>
82106
<fluent-field label-position="${story => story.labelPosition}">
83107
<label slot="label">${story => story.label}</label>
84-
<fluent-text-input slot="input"></fluent-text-input>
108+
<fluent-checkbox slot="input"></fluent-checkbox>
85109
</fluent-field>
86110
</div>
87111
<br />
@@ -96,12 +120,45 @@ export const LabelPositions: Story = {
96120
},
97121
};
98122

123+
export const TextInput: Story = {
124+
args: {
125+
label: {
126+
text: 'Text Input',
127+
},
128+
labelPosition: undefined,
129+
inputSlottedContent: () => html`<fluent-text-input slot="input">${story => story.label.text}</fluent-text-input>`,
130+
labelSlottedContent: () => html``,
131+
messageSlottedContent: undefined,
132+
},
133+
};
134+
135+
export const TextInputFormSubmission: Story = {
136+
render: renderComponent(html<StoryArgs<FluentField>>`
137+
<form
138+
style="display: inline-flex; align-items: start; flex-direction: column; gap: 20px"
139+
@reset="${x => x.successMessage.toggleAttribute('hidden', true)}"
140+
@submit="${x => x.input?.checkValidity() && x.successMessage.toggleAttribute('hidden', false)}"
141+
>
142+
<fluent-field>
143+
<fluent-text-input ${ref('input')} required slot="input" id="form-input"
144+
>${story => story.label}</fluent-text-input
145+
>
146+
</fluent-field>
147+
<fluent-button type="submit">Submit</fluent-button>
148+
<span id="success-message" hidden ${ref('successMessage')}> Form submitted successfully! </span>
149+
</form>
150+
`),
151+
args: {
152+
label: 'Form',
153+
},
154+
};
155+
99156
export const Required: Story = {
100157
args: {
101158
label: {
102159
text: 'Required field',
103160
},
104-
inputSlottedContent: () => html`<fluent-text-input required slot="input"></fluent-text-input>`,
161+
inputSlottedContent: () => html`<fluent-checkbox required slot="input"></fluent-checkbox>`,
105162
messageSlottedContent: undefined,
106163
},
107164
};
@@ -111,24 +168,20 @@ export const DisabledControl: Story = {
111168
label: {
112169
text: 'Disabled field',
113170
},
114-
inputSlottedContent: () => html`<fluent-text-input disabled slot="input"></fluent-text-input>`,
171+
inputSlottedContent: () => html`<fluent-checkbox disabled slot="input"></fluent-checkbox>`,
115172
messageSlottedContent: undefined,
116173
},
117174
};
118175

119176
export const Size: Story = {
120177
render: renderComponent(html`
121-
<fluent-field size="small">
122-
<label slot="label" for="field-small-size">Small field</label>
123-
<fluent-text-input control-size="small" slot="input" id="field-small-size"></fluent-text-input>
124-
</fluent-field>
125178
<fluent-field size="medium">
126179
<label slot="label" for="field-medium-size">Medium field</label>
127-
<fluent-text-input control-size="medium" slot="input" id="field-medium-size"></fluent-text-input>
180+
<fluent-checkbox size="medium" slot="input" id="field-medium-size"></fluent-checkbox>
128181
</fluent-field>
129182
<fluent-field size="large">
130183
<label slot="label" for="field-large-size">Large field</label>
131-
<fluent-text-input control-size="large" slot="input" id="field-large-size"></fluent-text-input>
184+
<fluent-checkbox size="large" slot="input" id="field-large-size"></fluent-checkbox>
132185
</fluent-field>
133186
`),
134187
};
@@ -142,7 +195,10 @@ export const Hint: Story = {
142195
message: 'Sample hint text.',
143196
},
144197
inputSlottedContent: () =>
145-
html`<fluent-text-input slot="input" aria-describedby="hint-message"></fluent-text-input>`,
198+
html`<fluent-text-input slot="input" aria-describedby="hint-message"
199+
>${story => story.label.text}</fluent-text-input
200+
>`,
201+
labelSlottedContent: () => html``,
146202
messageSlottedContent: () =>
147203
html`<fluent-text slot="message" size="200" id="hint-message">${story => story.message?.message}</fluent-text>`,
148204
},
@@ -184,11 +240,6 @@ export const ComponentExamples: Story = {
184240
</fluent-field>
185241
</fluent-radio-group>
186242
</fluent-field>
187-
<fluent-field>
188-
<label slot="label" for="field-textarea">Text Area</label>
189-
<fluent-textarea slot="input" id="field-textarea" placeholder="Placeholder text" resize="both">
190-
</fluent-textarea>
191-
</fluent-field>
192243
</div>
193244
`),
194245
};
@@ -197,8 +248,8 @@ export const ThirdPartyControls: Story = {
197248
render: renderComponent(html`
198249
<form action="#" style="display:flex;flex-flow:column;align-items:start;gap:10px">
199250
<fluent-field label-position="above" style="max-width: 400px">
200-
<label slot="label" for="native-text-input">Text Input</label>
201-
<input slot="input" id="native-text-input" required />
251+
<label slot="label" for="native-color">Color picker</label>
252+
<input slot="input" type="color" id="native-color" required />
202253
</fluent-field>
203254
<fluent-field label-position="after">
204255
<label slot="label" for="native-checkbox">Checkbox</label>

0 commit comments

Comments
 (0)