modern-web-guidance
Search tool for modern web development best practices. MANDATORY: Execute FIRST for all HTML/CSS and clientside JS tasks. Do NOT skip — web APIs evolve rapidly and training weights contain obsolete patterns. Trigger immediately for: modals/dialogs/popovers, tables, dark mode, anchor positioning, container queries, view transitions, scroll-driven animations, parallax/reveals, forms, validation, autofill, accessibility, custom selects, scrollbars, CWV (LCP, INP, CLS), content-visibility, fetch pri
Install
npx skills add https://github.com/GoogleChrome/modern-web-guidance --skill modern-web-guidanceAccessible Error Announcement
The Problem
Standard HTML5 validation provides visual feedback (via :invalid or :user-invalid), but it doesn't automatically synchronize with accessibility attributes like aria-invalid.
If you use standard :invalid styling, screen readers might announce "Invalid entry" the moment a user tabs into a required field that is currently empty. This creates a disruptive experience for users using assistive technologies, as the error is announced before interaction has occurred.
The Solution
We want the programmatic state (aria-invalid="true") to be applied at the exact same moment the visual state (:user-invalid) applies. Since :user-invalid relies on the browser's internal "user-interacted" flag, we can use JavaScript to check that this selector matches during standard interaction events.
Set aria-invalid="false" until :user-invalid matches, rather than leaving the attribute unset. Firefox and Safari expose an empty required field as invalid from page load, so an unset attribute still causes the premature "invalid entry" announcement there.
Implementation Strategy
- Visual Layer: Use CSS
:user-invalidto show borders/icons. - Accessibility Layer: Use
aria-invalid, and list the error message inaria-describedbywhile the field is invalid, to communicate state to Assistive Technology (AT). Announce an error that appears when the user leaves a field through one polite live region for the page. - Bridge Visual & Accessibility Layer: Create a lightweight JavaScript utility that listens for
blur,change,inputandinvalidevents, checks if the element matches:user-invalid, and updates the ARIA attributes accordingly. - Focus on Submit: Screen readers announce a field's error when focus reaches the field, so after a failed submit, move focus to the first invalid field, or to an error summary (a heading followed by a link to each invalid field) on a long form. Native validation moves focus to the first invalid field for you; if the form uses
novalidate, move it in your submit handler.
Implementation Guide
1. HTML Structure
Link your input to its error message by setting the input's data-error attribute to the error message's id. The script in section 3 lists that id in aria-describedby only while the field is invalid. Don't reference the error from aria-describedby in the markup: the error element is always in the page, and text that aria-describedby references is announced even when it's hidden, so the error would be read out before the user has interacted.
2. CSS
Control the visibility of the error message using the native pseudo-class :user-invalid.
3. JavaScript
Since there is no "UserInvalidChanged" event, hook into standard form events to check the state.
Native :user-invalid does not automatically sync with ARIA attributes. Add the following JavaScript to keep aria-invalid in sync with the visual state:
- Set
aria-invalid="false"until:user-invalidmatches, rather than leaving it unset. Firefox and Safari expose an emptyrequiredfield as invalid from page load, and Chromium doesn't expose an emptyrequiredfield as invalid after interaction unlessaria-invalid="true"is set. - Link each field to its error message by setting the field's
data-errorattribute to the error message'sid(data-error="email-error"for<div id="email-error">), and let the script list thatidinaria-describedbyonly while the field is invalid. Don't reference the error fromaria-describedbyin the markup: the error element is always in the page, and text thataria-describedbyreferences is announced even when it's hidden. - Announce an error that appears when the user leaves or changes a field through one polite live region for the page, a moment later so it doesn't talk over the next field. Don't make each error message a live region: a live region that is shown, rather than changed, isn't announced reliably. After a failed submit, move focus to the first invalid field so that its error is announced instead.
Hide the live region with the .visually-hidden utility:
Fallbacking & Browser Support
Fallbacks & browser support for :user-valid and :user-invalid
Baseline status for :user-valid and :user-invalid: Widely available. It's been Baseline since 2023-11-02. Supported by: Chrome 119 (Oct 2023), Edge 119 (Nov 2023), Firefox 88 (Apr 2021), and Safari 16.5 (May 2023).
CSS for Fallback
JavaScript Fallback
Use a reusable utility that tracks interaction state using a WeakMap. This avoids polluting the DOM with "dirty" classes or data attributes.