Skip to content

fix(many): stop wrapping form field controls in their label - #2740

Open
matyasf wants to merge 2 commits into
masterfrom
form_label_dom_order_fix
Open

matyasf wants to merge 2 commits into
masterfrom
form_label_dom_order_fix

Conversation

@matyasf

@matyasf matyasf commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • FormFieldLayout v2 renders a div and puts only the label text in a <label for={id}>, so messages and before/after content stay out of the control's accessible name (see the JIRA ticket and Slack discussions)
  • Messages reach the control through aria-describedby via a new { describedBy } children render function on FormField, FormFieldLayout, and FormFieldGroup. This means that TextInput, TextArea, NumberInput, RangeInput, DateInput, RadioInputGroup, and CheckboxGroup v2 use the render function instead of their own messages/label id wiring
  • Clicks on icons or padding around the control are forwarded to it, since the wrapping label no longer does that automatically

Test Plan

  • With VoiceOver and NVDA, check that TextInput, TextArea, NumberInput, and RangeInput announce only the label as the name, and announce messages as the description
  • Click the label, the icons, and the padding of TextInput, Select (down arrow), and NumberInput; each should focus the control
  • Click the DateInput calendar icon; it should open the calendar instead of focusing the input
  • Check that RadioInputGroup, CheckboxGroup, and FormFieldGroup still read the group messages on each control

Fixes INSTUI-5205

🤖 Generated with Claude Code

@matyasf matyasf self-assigned this Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2740/

Built to branch gh-pages at 2026-10-06 15:51 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

github-actions Bot pushed a commit that referenced this pull request Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Visual regression report

Cypress suite: ✅ Passing

Visual diff: ✅ No changes.

Status Count
Unchanged 99
Changed 0
New 0
Removed 0

Accessibility (axe): ✅ No violations.

📊 View full report — click a screenshot's ⚠ badge to see each violation boxed on the image, with the offending element named and contrast failures shown as color swatches.

Baselines come from the visual-baselines branch. They refresh on every merge to master. The Cypress suite line covers the a11y and console-error assertions — a ❌ there means the suite found real issues even if the visual diff is clean.

Comment on lines +84 to +87
beforeEach(async () => {
await commands.resetMouse()
})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated test fix. the mouse is now in a different position due to new tests, these tests were passing accidentally because the mouse was left at a 'good' spot by previous test

github-actions Bot pushed a commit that referenced this pull request Oct 2, 2026
Comment on lines -83 to -85
get hasMessages() {
return this.props.messages && this.props.messages.length > 0
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a common pattern. Controls need to use aria-describedby for their messages, so they need to know whether there are any.

This is now much simpler since its a parameter for their children (see below)

Comment on lines 51 to 55
* id for the label element. Useful when the control is not a labelable
* element (e.g. a custom widget), and needs to reference the label via
* `aria-labelledby`.
*/
labelId?: string

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer used anywhere. Kept it to not introduce a breaking change

* Not used when rendering as a `fieldset` (group)
*/
id?: string
id: string

@matyasf matyasf Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This became mandatory. While this is technically a breaking change, its an internal component that I dont expect that anyone uses. Also keeping this optional would be a source of nasty bugs

@matyasf
matyasf force-pushed the form_label_dom_order_fix branch from ab2e75e to 3e87b7a Compare October 2, 2026 12:32
@matyasf
matyasf requested a review from HerrTopi October 2, 2026 12:38
github-actions Bot pushed a commit that referenced this pull request Oct 2, 2026
@matyasf
matyasf force-pushed the form_label_dom_order_fix branch from 3e87b7a to 5a79918 Compare October 2, 2026 12:46
github-actions Bot pushed a commit that referenced this pull request Oct 2, 2026
matyasf and others added 2 commits October 6, 2026 17:30
Previously FormFieldLayout wrapped the label text, the control, and the messages in one
`<label>`, so screen readers read the messages and any before/after content as part of the
control's accessible name. Only the label text sits in the `<label>` now, linked to the control
by `for`, and the messages reach the control through `aria-describedby` via the new
`{ describedBy }` children render function.

The wrapping label also focused the control when someone clicked an icon or the padding around
it. FormFieldLayout forwards those clicks by hand to keep that behavior.

Fixes INSTUI-5205

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mouse keeps its position across test files. When an earlier file left it
where ColorPreset renders its second row of indicators, that indicator's
tooltip opened over the first row, and the click on the indicator above it
timed out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@matyasf
matyasf force-pushed the form_label_dom_order_fix branch from 5a79918 to 9cbd883 Compare October 6, 2026 15:47
Comment on lines 62 to 67
/**
* id for the label. If empty, it's auto generated
* id for the label element. If empty, it's auto generated.
* useful if you use `aria-labelledby={labelId}` to label something with the
* label element.
*/
labelId?: string

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually no longer needed, I just did not want to take it out because it's be a breaking change. It was added recently, by #2730

github-actions Bot pushed a commit that referenced this pull request Oct 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant