Skip to content

Floating labels: place label before control for screen readers#42539

Open
mdo wants to merge 1 commit into
v6-devfrom
mdo/a11y-screenreader-fix
Open

Floating labels: place label before control for screen readers#42539
mdo wants to merge 1 commit into
v6-devfrom
mdo/a11y-screenreader-fix

Conversation

@mdo

@mdo mdo commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #41362.

Floating labels currently require the control to come before the <label> in the DOM so the floating animation can use a sibling (~) selector. That ordering causes screen readers (e.g. NVDA) to announce the label after the field's value rather than before it — an accessibility concern flagged in #41362.

This switches the SCSS to look forward with :has() so the <label> can come first in the DOM (announced first by screen readers) while the CSS still reacts to the control's state (:focus, :not(:placeholder-shown), :disabled, :-webkit-autofill).

:has() has been Baseline since late 2023, which is what makes this viable for v6.

Changes

  • scss/forms/_floating-labels.scss — every control ~ label selector flipped to label:has(~ control).
  • Markup reordered to label-first (required, since the old input-first order no longer floats):
    • site/src/content/docs/forms/floating-labels.mdx (examples + explanatory note)
    • site/src/content/docs/forms/validation.mdx
    • Example pages: sign-in, heroes, dialogs, cheatsheet
    • js/tests/visual/floating-label.html
  • scss/forms/_form-control.scss — give .form-control-plaintext its own copy of the control tokens so its var(--control-*) references resolve, fixing phantom borders and label misalignment.

Notes

  • Visually a no-op for layout — the label is position: absolute, so source order doesn't change positioning.
  • This drops floating-label support for browsers without :has(); fine for v6's browserslist.
  • SCSS compiles clean and passes stylelint.

Floating labels required the control to precede the `<label>` in the DOM
so the floating animation could use a sibling (`~`) selector. That order
made screen readers (e.g. NVDA) announce the label after the field's
value instead of before it.

Switch the SCSS to look forward with `:has()` so the `<label>` can come
first in the DOM while the CSS still reacts to the control's state
(focus, value, disabled, autofill). Reorder all examples, docs, and the
visual test to label-first markup to match.

Also give `.form-control-plaintext` its own copy of the control tokens so
its `var(--control-*)` references resolve, fixing phantom borders and
label misalignment.

Fixes #41362
@mdo mdo requested a review from a team as a code owner June 22, 2026 05:51
@mdo mdo added this to v6.0.0 Jun 22, 2026
@github-project-automation github-project-automation Bot moved this to Inbox in v6.0.0 Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

1 participant