Form
Forms
Open in Storybook: Form (opens in a new tab)
Overview
A form is native controls, each with a visible label and one message above it. Focus goes where the problem is, and what arrives without focus is said with ariaNotify() on the field: no live region, no toast.
The rule. Pick the control that makes the mistake impossible, then validate what is left. Every control has a visible label, “(required)” or “(optional)” in its text, and one message above it. Judging never moves focus; only a submit does: one error → the field, two or more → the error list heading. Results that arrive without focus are said by one ariaNotify() call on the field; the same words are already in its visible message.
Anatomy
- Form
form novalidate aria-labelledbyNamed by its heading, so it is a landmark: name only the page’s main form.
required,min,maxandtypestay: they still drive the checks and autofill. - Heading
h2·tabindex="-1"Names the form. Its level comes from the outline of the page. Focusable by script, so Back can return to it.
- Convention
p“(required)” or “(optional)”, said once at the top. Then only the optional fields say what they are.
- Error list
section>h3+ulAfter a failed submit with two or more errors, at the top and never sticky. The heading takes focus; each link focuses its field; items leave as they are fixed.
- Label
label forVisible, before the control, with “(required)” or “(optional)” in its text. A placeholder is not a label.
- Message
p idOne for each field: the hint, then “Error: …”, “Correct.” or “Fixed. It is correct now.” The same id in every state.
- Control
input aria-invalid aria-describedbyA native control.
aria-invalidis always there, andaria-describedbypoints at the message. - Help
detailsBeside a hard field: explains what is hard to answer (3.3.5).
- Submit
button type="submit"Named for the action, never “Submit”. Never disabled to force a valid form.
Usage
Use cases
Fields with their hint and an error that gives the fix. Label, format and limits are in the hint before the mistake. Each is the real form and works: answer, pause or submit.
Email
Suggest the domain.
type="email"·autocomplete="email"· a hint, then the errorThe error says what is wrong and the fix: “Did you mean ana@gmail.com?”
Phone
Format with the country code.
type="tel"· the format in the hintNever
type="number": a phone has a + and spaces.Date
Range in the hint, nearest valid in the error.
type="date"·minandmaxTyping must work. The error offers the next valid day.
Password
Rules in the hint.
autocomplete="new-password"· Show is a button witharia-pressedPaste is allowed. Repeat the password only for security.
Textarea
Count what is missing.
textarea· the limit in the hint“You have 9, so add 11 more.” says how far, not only that it is short.
File
Name, limit, fix.
type="file"·accept· the types and the size in the hintChoose a HEIC picture to see the error: it names the file and the fix.
Colour
Offer one that passes.
type="color"· the value shown as textThe error measures the contrast and offers the nearest colour that passes.
Taken name
Offer two free ones.
autocomplete="username"·spellcheck="false"Try “ana”, “admin” or “mica”: they are taken.
One of 2 to 6
Radios in a fieldset.
fieldset+legend· one shared messageThe options are label and input as siblings; each carries
aria-invalid.One of about 7 or more
A select, first option empty.
select· the first option has no valueNever
select multiple: one arrow key replaces the whole selection.Several
A checkbox group.
fieldset+legend· limits in the hint and the errorA group cannot be
required: the legend says it.Booking
A whole form: errors, review and result.
formnamed by its heading ·novalidate· the error listSubmit it empty, then with one mistake, then right.
Input types
The rule. Pick the type for the data, not for the look. The type sets the role, the keys and the mobile keyboard. When a type fights the user (number for a card number, date for a birth date), use type="text" with inputmode and autocomplete instead. Every type still needs a visible label. All of them follow the field rules: one message above the control, aria-describedby, novalidate with our own messages.
type="text"
Any short text: names, postcodes, card numbers.
Role: textbox
Any text · default keyboard
type="email"
An email address.
Role: textbox
@ keyboard ·
autocomplete="email"type="password"
A secret.
Role: textbox, protected
autocomplete="current-password"· paste allowedtype="tel"
A phone number.
Role: textbox
Phone keypad ·
autocomplete="tel"type="url"
A web address.
Role: textbox
/ and .com keys ·
autocomplete="url"type="search"
A search query.
Role: searchbox
Enter submits · Esc may clear · in
searchtype="number"
A count: quantity, guests.
Role: spinbutton
↑ ↓ step ·
min,max,stepannouncedtype="range"
An approximate value.
Role: slider
← → ↑ ↓ · Home · End · Page Up/Down
type="date"
A calendar date.
Role: date (segments: day, month, year)
Type each segment · ↑ ↓ change it · picker button
type="time"
A time of day.
Role: time (segments)
Type or ↑ ↓ per segment ·
stepfor secondstype="datetime-local"
Date and time, no zone.
Role: date and time (segments)
No time zone: say which one
type="month"
Month and year.
Role: month and year
Not in Firefox or desktop Safari: falls back to text
type="week"
ISO week and year.
Role: week and year
Rarely understood; not in Firefox or desktop Safari
type="color"
A colour value.
Role: button that opens a colour picker
Enter/Space opens the OS picker · value read as hex
type="checkbox"
On/off, or many of a set.
Role: checkbox
Space toggles · Tab to each one
type="radio"
One of a set.
Role: radio, in a group
One Tab stop · arrows move and select
type="file"
An upload.
Role: button, named by label + chosen file
Enter/Space opens the file dialog ·
accept,multipletype="submit"
Sends the form.
Role: button
Enter in a field submits ·
valueis the nametype="reset"
Clears every field.
Role: button
Clears every field: avoid
type="image"
A graphical submit.
Role: button (submit)
altis the name: required
| Type | Use for | autocomplete | Watch out |
|---|---|---|---|
text | Any short text: names, postcodes, card numbers | name, postal-code, cc-number… | Add inputmode="numeric" for digits that are not quantities |
email | An email address | email | Native messages are vague: novalidate + our message. spellcheck="false"; multiple for a list |
password | A secret | current-password, new-password | Never block paste (3.3.8). Rules visible before typing. Show password: a button with aria-pressed |
tel | A phone number | tel | Accept spaces, +, brackets; no pattern that rejects real numbers |
url | A web address | url | Needs the scheme: say “Start with https://” or add it for the user |
search | A search query | off or a custom token | Put it in search or form role="search"; visible label or a labelled button; a real Clear button |
number | A count: quantity, guests | — | Not for card numbers, postcodes, phone or IDs (leading zeros, spinners, wheel). Use text + inputmode="numeric" |
range | An approximate value | — | Show the value as text; aria-valuetext when a unit matters (“40 percent”). Pair with number when precision matters |
date | A calendar date | bday (prefer three fields) | Format follows the browser language; the picker is not fully accessible everywhere. Birth dates: day, month, year text fields |
time | A time of day | — | 12/24 h follows the locale. step="1" adds seconds |
datetime-local | Date and time, no zone | — | Has no time zone: state it in the hint |
month | Month and year | cc-exp (prefer text MM/YYYY) | Not in Firefox or desktop Safari: falls back to text, so always give the format |
week | ISO week and year | — | Same support gap; few people know their week number. Prefer a date |
color | A colour value | — | The value is read as hex: show a name too. Fixed choices: radios as swatches |
checkbox | On/off, or many of a set | — | Many: fieldset + legend. indeterminate only through script. One required box (terms) needs its own message |
radio | One of a set | — | fieldset + legend, one name. Arrowing selects, so never act on change (3.2.2) |
file | Upload | — | Keep the native input, even disabled: never display: none. Style ::file-selector-button. Say type and size in the label; list chosen files as text. Drag and drop is an extra, not the only way |
hidden | Data the server needs | — | Not for anything the user must know; not a security measure |
submit | Sends the form | — | Prefer button type="submit": it can hold markup |
reset | Clears every field | — | Avoid: one slip wipes the form. If needed: confirm or undo |
button | A scripted action | — | Prefer button type="button" |
image | A graphical submit | — | alt is the name and is required. Prefer button type="submit" with text |
Props
| Prop | What it does | Default |
|---|---|---|
Form title · level | The name of the form: its heading, and its level. The heading of the list of errors is one under it. Left out, the level FormLevel gives | 2 |
Form convention · marks | Said once at the top. With it, only an optional field says what it is; marks (optional or both) sets that by hand | follows convention |
Form submit | The submit button, named for the action | — |
Form onValid · onInvalid | A submit with every field right, with the answers; and one that found something wrong | — |
Form startWith | Errors the form opens with, by field name: the state of a drawing or a demo. Nothing is said for them | — |
Field name · label · hint | The name of the control, what it asks (the form adds “(required)” or “(optional)”), and the format and limits before the mistake | Required: name, label |
Field type · as | An input type, checkbox for one box, or as a textarea or a select (with options and empty, the words of its empty first option) | text |
Field required · missing | Whether an empty answer is wrong, and the fix it says | true |
Field check | (value, answers) => string: empty when the answer is right, else what is wrong and the fix | — |
Field dependsOn | Fields this one is checked against: it is looked at again when one changes | — |
Field help | { summary, body }: a details beside a hard field | — |
Field unit · valueText | For a range: the unit drawn after its value, and the value in words | — |
FieldGroup | name, legend, kind (radio or checkbox), options, inline, and check(values, answers) | — |
Review · Result · Success | The step before the result, the result, and the “Success:” line. A heading takes focus as it appears (focus), and is described by its line | — |
Example
import { Field, FieldGroup, Form } from '@/components/Form'
<Form
title="Book a tasting"
convention="All fields are required unless marked (optional)."
submit="Review my booking"
onValid={(answers) => review(answers)}
>
<Field
name="email"
label="Email"
type="email"
hint="For your receipt. Example: ana@example.com"
autoComplete="email"
check={(value) =>
value.includes('@') ? '' : 'Email has no @. Add it, as in ana@gmail.com.'
}
/>
<Field name="phone" label="Phone" type="tel" required={false} />
<FieldGroup
name="seats"
legend="Seats"
kind="radio"
inline
hint="Up to 4 per booking."
options={[
{ value: '1', label: '1' },
{ value: '2', label: '2', defaultChecked: true },
]}
/>
</Form>Specs
Structure
Per field, in order: label, hint or message, control, help. One column. Submit named for the action.
| Part | Rule |
|---|---|
| Field order | Label, hint or message, control, help. One column; two only when the DOM order stays row by row. |
fieldset + legend | One question with several controls: radios, checkboxes, a split date. Sections of different fields: heading + list, not a fieldset. Label and input as siblings, aria-invalid and aria-describedby on every option, one shared message. A checkbox group cannot be required: say it in the legend. |
No select multiple | A checkbox group instead: one arrow key in a multiple select replaces the whole selection. |
| Label + input | Siblings: label for + id. No input inside its label. |
| Optional | “(optional)” in the label. Empty is never “correct”: say nothing. |
autocomplete | name, email, tel, country, street-address… on personal data (1.3.5). |
| Never | A placeholder as label, a floating label, a div rebuilt as a select, an input inside its label. |
States
| State | Signals | Forced colours |
|---|---|---|
| Default | The hint only. aria-invalid="false". | — |
| Error | “Error:” prefix · red text · 4px bar · 3px border. aria-invalid="true". | Prefix and border width survive; the bar is a border-inline-start, not a background. |
| Correct | The words “Correct.” in green, once judged. | Words carry it. |
| Fixed | The words “Fixed. It is correct now.” in green, after an error. | Words carry it. |
| Focus | The site’s ring on the control. Never removed. | outline, never box-shadow alone. |
| Error list | Heading · links underlined · 6px bar. | Links keep LinkText. |
Layout
| Width | Layout | What changes |
|---|---|---|
| 1280 CSS px · 100% | One column of fields, max 40rem | Error list above the first field. Buttons in DOM order. |
| 640 CSS px · 200% | Same column, full width | Nothing moves sideways; messages stay above controls. |
| 320 CSS px · 400% (1.4.10) | One column, buttons full width | List items wrap. No toast or sticky bar that could cover the focused control (2.4.11). |
| 390 · mobile | Same as 320 + on-screen keyboard | The focused field scrolls above the keyboard; the error list scrolls with the page. |
| Text 200% (1.4.4) | Control height from content | Messages and labels wrap; no fixed heights, no truncation. |
Markup
<form novalidate aria-labelledby="book-h">
<h2 id="book-h" tabindex="-1">Book a tasting</h2>
<label for="email">Email (required)</label>
<p id="email-msg">For your receipt. Example: ana@example.com</p>
<input id="email" type="email" required autocomplete="email"
aria-invalid="false" aria-describedby="email-msg" />
<fieldset>
<legend>Sessions (choose 1 or 2, required)</legend>
<p id="ses-msg">Error: You chose 3 sessions. Untick 1 session.</p>
<input id="s1" type="checkbox" name="ses" aria-invalid="true" aria-describedby="ses-msg" />
<label for="s1">Monday</label>
…
</fieldset>
</form>Tokens
Component tokens
| Part | Property | Token | Value |
|---|---|---|---|
| Error | Text, bar and border | — | #c8102e · 5.9:1 on white |
| Success | Text and bar | — | #1a7f37 · 5.1:1 on white |
| Control | Border | ink-soft | 1.5px · #4b4b55 · 8.6:1 on white |
| Control | Corner | radius | 8px |
| Error control | Border | — | 3px, the padding gives back what it takes |
| Message | Bar | — | 4px |
| Error list | Bar | — | 6px |
| Focus | Ring | focus-ring-primary | The site’s ring: #0066ff, 3:1 or more against both |
Guidelines
Controls by scenario and context
Pick the control that makes the mistake impossible, then validate what is left.
| Scenario | Control | Rule, and the error to give |
|---|---|---|
| Short text (name) | type="text" | autocomplete, spellcheck="false". Say what is wrong with the value. |
type="email" | Suggest the domain: “Did you mean ana@gmail.com?” | |
| Phone | type="tel" | Format with country code in the hint. Never type="number". |
| URL | type="url" | “Start with https://” |
| Digits that are not a quantity (code, ID) | type="text" + inputmode="numeric" | Not type="number". |
| Quantity with limits | type="number" + min, max | Quote the value and the limit. Under about 5 values: radios. |
| Date, time | type="date" | "time" | "datetime-local" + min, max | Range in the hint, nearest valid in the error. Typing must work. |
| Week, month | type="week" | "month" | A text box in some browsers: hint the format. |
| One of 2 to 6 | Radios in a fieldset | One shared message. |
| One of about 7 or more | select, first option empty | “Choose a country.” |
| One of many, typed | input + datalist | Suggestions are not validation: check the answer. |
| Several | Checkbox group | Limits in hint and error. Never select multiple: one arrow key replaces the selection. |
| One yes/no (terms) | Single checkbox | aria-invalid on it. |
| On/off, acts at once | Switch | In a form that sends later, a checkbox. |
| Secret | type="password" | Rules in the hint. Show is a button with aria-pressed. autocomplete="new-password". |
| Long text | textarea | Limit in the hint. “You have 9, so add 11 more.” |
| File | type="file" + accept | Types and size in the hint. Error: file name, limit, fix. |
| Approximate value on a scale | type="range" + a value as text | aria-valuetext with the unit. Exact value: number. |
| Colour | type="color" | Measure contrast, offer one that passes. |
| Username | type="text" | Taken: offer two free ones. |
| Context | Do |
|---|---|
| Acts at once (filter, sort) | select or buttons. No error flow. |
| Sends later | Submit, validate, review. |
| Money, legal, data change | Review step: confirm or reverse (3.3.4). |
| Sign-in, sign-up | autocomplete: username, current-password, new-password, one-time-code. Paste allowed. No puzzle or retyping from memory (3.3.8). No suggestion that leaks (3.3.3). |
| Personal data | autocomplete token (1.3.5). |
| Already known or entered | Prefill, do not ask again. Repeat only the password (3.3.7). |
| Depends on another field | Always in the DOM. The hint says when it is needed. Recheck when the other changes. |
| Optional | “(optional)”. Empty is never “correct”. |
| Submitting takes time | Show “Sending…” on the page and call ariaNotify() on the submit button, then the result. Keep the answers on failure. |
| Time limit | Warn, let extend, keep the answers (2.2.1). |
| Several steps | Validate each step, focus its heading, Back keeps answers. |
| In a dialog or drawer | closedby="closerequest". Plain form if it must stay open. Cancel with formnovalidate. |
| Banner after submit | One ariaNotify() on the submit button: normal for the result, high only when sending failed and the next step is the user’s. Dismiss moves focus to a real control. |
| File drop zone | Real type="file" first. Drag is extra (2.5.7). |
| Shown, not editable | readonly, not disabled: disabled leaves the tab order and the submit. Say why in the hint. |
| Search | search or role="search", labelled input, button named Search. |
| Narrow screen | One column, 24 px targets. |
Message
What is wrong, then the fix. In the list: “Field: message”.
| Field | Don’t | Do |
|---|---|---|
| Invalid input | Error: Email has no @. Add it, as in ana@gmail.com. · Did you mean ana@gmail.com? | |
| Date | Invalid date | Error: That day is a Sunday. Try Monday, 23 November. |
| Sessions | Too many selected | Error: You chose 3 sessions. Untick 1 session. |
| Comment | Too short | Error: You have 9 characters, so add 11 more. |
| Username | Not available | Error: “ana” is taken. Try ana.r or ana23. |
| Sign in | Wrong password for ana@gmail.com | Error: Email or password is not right. No suggestion: it would leak which one. |
| Error list item | Please fix the errors | Email: Email has no @. Add it, as in ana@gmail.com. |
Debounce
Delay the judging, never the value in the field or the focus ring. No success criterion or numbered technique asks for it: it serves 3.3.1, 4.1.3 (ariaNotify(), with the words on the page) and 3.2.2.
| Rule | How |
|---|---|
| Written inputs wait | text, email, tel, url, number, search, password, textarea. Judge 1 s after the last key. Every key restarts the wait. |
| Not waited for | Blur and submit (at once). Choices: select, radio, checkbox, date, time, colour, range. A press that moves focus waits for the click, so a message does not move the button from under the pointer. |
| Cancel pending waits | Submit, reset, refill and unmount cancel them. No timer outlives its form. |
| A fix shows at once | The error leaves and green appears as soon as the value is right. Only saying it waits. |
| Speak only on change | Never repeat “correct” at each pause. |
| A pause is a hint, not a verdict | Slow typing, dictation and switch users stop mid-answer. The result never takes focus, disables submit or blocks it. Submit checks everything. |
| Never delay | The value in the field and the focus ring. |
Success and review
A valid submit goes to a review step. Confirm goes to a result. Both headings take focus and are described by a “Success: …” line that uses the person’s answers.
| Rule | How |
|---|---|
Success is a p under the heading | <p>Success: …</p>, green bar. The heading that takes focus has aria-describedby to it. |
| Use the person’s answers | Seats, date, total, where it goes: “2 seats on Monday, 23 November. Total 48 €. Tickets go to ana@gmail.com.” |
| Review shows everything | Every answer, the total, Confirm, Back. Cancel after confirming (3.3.4, 3.3.6). |
| Change buttons say what they change | aria-labelledby="own-id question-id": heard as “Change Seats”. Change moves focus to that field. |
| Never ask twice | Answers carry over; nothing is typed again (3.3.7). |
Feedback by scenario and context
Each row is one scenario from Focus and what is said. Each column shows what that moment gets at that size. The drawings are the real components, small: the field, the list of errors, the buttons, the Success line and the ariaNotify() call.
- Solid: required in that context, never dropped
- Dashed: optional, or only if there is room
- ariaNotify()Said with
field.ariaNotify(): no element, nothing added to the DOM; the words are in the visible message
Zoom 400% · 320 CSS px. At 200% text messages wrap above the control.
Submit, one error
Checkout · one field wrong
- Desktop · 640 px and upFull width, one column of fields
Field (focused) · message · Continue
Focus moves to the field; its message is read with it. No list for one error, no ariaNotify().
- Smaller viewport · 400–639 pxNarrow column or card
Field · Continue
Same. Message stays above the control.
- Zoom 400% · 320 CSS pxReflow
Field · Continue
One column. The message wraps; nothing floats over the field.
Submit, two or more
Long form
- Desktop · 640 px and upFull width, one column of fields
Error list (heading focused) · fields
Errors rendered first, then focus to the list heading. Links focus the field. Items leave as they are fixed.
- Smaller viewport · 400–639 pxNarrow column or card
Error list · fields
List at the top of the form, never sticky.
- Zoom 400% · 320 CSS pxReflow
Error list · fields
List items wrap; heading still takes focus.
Typing
Email, then a pause
- Desktop · 640 px and upFull width, one column of fields
Field · message · ariaNotify()
ariaNotify()
Visible fix at once. Spoken 1 s after the last key (every key restarts the wait), or at once on blur.
- Smaller viewport · 400–639 pxNarrow column or card
Field · ariaNotify()
ariaNotify()
Same.
- Zoom 400% · 320 CSS pxReflow
Field
ariaNotify()
Same; the call does not depend on the viewport.
Fixing a listed error
Two were listed
- Desktop · 640 px and upFull width, one column of fields
Field · “Fixed.” · count · ariaNotify()
ariaNotify()
Choosing speaks at once. The count is read from the list as drawn and shown on the field fixed last.
- Smaller viewport · 400–639 pxNarrow column or card
Field · ariaNotify()
ariaNotify()
Same.
- Zoom 400% · 320 CSS pxReflow
Field
ariaNotify()
Count in the message under the field, never a sticky counter.
Sign-in failed
No suggestion: it leaks
- Desktop · 640 px and upFull width, one column of fields
Field (focused) · message · Sign in
One error, so focus goes to the email field. Paste and password managers allowed (3.3.8).
- Smaller viewport · 400–639 pxNarrow column or card
Field · Sign in
Same.
- Zoom 400% · 320 CSS pxReflow
Field
Same.
Review step
Valid submit
- Desktop · 640 px and upFull width, one column of fields
Review heading (focused) · answers · Change · Confirm · Back
Heading described by “Success: Your answers are saved. Check them, then confirm.” Change → that field.
- Smaller viewport · 400–639 pxNarrow column or card
Heading · answers · Change · Confirm
Answers stack under each question.
- Zoom 400% · 320 CSS pxReflow
Heading · answers · Change · Confirm
Buttons full width, 44 px tall, in DOM order.
Confirmed
Result page
- Desktop · 640 px and upFull width, one column of fields
Result heading (focused) · Success line
“Success: what happened, what is next”, with the person’s answers. Same words every time.
- Smaller viewport · 400–639 pxNarrow column or card
Result heading · Success line
Same.
- Zoom 400% · 320 CSS pxReflow
Result heading · Success line
Same; no toast.
Input attributes
| Attribute | Does | Rule |
|---|---|---|
label for / id | The accessible name | Always visible. placeholder is not a label |
autocomplete | Lets browsers and assistive technology fill and identify the purpose | Required for personal data (1.3.5). Use the exact token |
inputmode | Mobile keyboard only, no validation | numeric, decimal, tel, email, url, search, text, none |
enterkeyhint | The Enter key label on mobile | enter, done, go, next, previous, search, send |
required | Required state; native check | Also “(required)” in the visible label; our own message with novalidate |
aria-describedby | Hint and message read after the name | Points at the one message above the control |
aria-invalid | Invalid state | Always present: true after a submit or after typing stops, false otherwise |
readonly / disabled | Readonly: focusable, read, submitted. Disabled: skipped, not submitted | Prefer readonly for values users need to read |
maxlength | Silently stops typing | Show the limit and a count; let them paste then explain |
pattern + title | Native check; title is not a reliable message | Our own message in the field’s message slot |
spellcheck, autocapitalize | Correction on mobile | Off for email, codes, usernames |
| Element | Heard as | Rule |
|---|---|---|
textarea | edit text, multi-line | Label and message like any field; rows for the visible height; resize: vertical |
select | combo box / pop-up button | Native first; size or multiple make it a list box. Option text is the name |
datalist | suggestions on a text input | Support for screen readers and zoom is uneven: use a combobox pattern when it matters |
button | button | type always set: submit, button or reset |
fieldset + legend | group with a name | Every set of radios or checkboxes, and related fields such as an address |
output | status (role="status") | A live region: not for form feedback. Use ariaNotify() |
Accessibility
Focus and what is said
Where focus goes and what is said, for every moment of the form.
| When | Focus | Said |
|---|---|---|
| Submit, one error | The field | Focus reads it: label, invalid, the message. |
| Submit, two or more | The error list heading | Focus reads it. No ariaNotify(). |
| Typing | Stays | ariaNotify() on the field, after 1 s pause or blur: “Email: not correct. …”, “Email: correct.”, “Email: fixed. It is correct now.” |
| Choosing (select, radio, checkbox, date) | Stays | Same, at once. |
| Fix, two or more were listed | Stays | Adds “N errors left.” or “No errors left.” Shown on the field fixed last. |
| Optional field empty | Stays | Nothing. |
| Valid submit, review step | The review heading | Heading, described by “Success: …”. |
| Change | That field | Focus reads it. |
| Confirm, Cancel | The result heading | Heading, described by “Success: what happened, what is next”. |
| Form reset or refilled | Stays | Clear messages, count and spoken state. |
Announcements
Focus is the announcement for headings and for the field it lands on. Everything that arrives without focus is said with element.ariaNotify(): nothing enters the DOM, no live region. The same words are already in the field’s visible message.
| Rule | Why |
|---|---|
field.ariaNotify(text, { priority: 'normal' }) | Host: the field the message is about. It is in the accessibility tree, it stays, and its lang sets the voice. There is no element to add and nothing to keep empty. |
| Never call it for what focus reads | Focus already reads headings and fields; calling too says everything twice. |
| Written inputs: see Debounce | Choices are said at once. |
| Speak only on change | One call per event; normally only the latest call is spoken. Texts that settle together are one string. |
| Count from the list as drawn | “2 errors left.” is read from the list items, never from a stored number. |
No role="alert", no live region, no fallback | An alert re-reads the list at every fix; a live region is a second channel. ariaNotify() only. |
| Page first, call second | Write the visible message and set aria-invalid, then call. Never on load. |
| The words are on the page | The call says the label plus the state text that is already visible in the message (4.1.3). It cannot be reread, so nothing exists only in speech. |
normal; high only when sending failed | high interrupts speech: only when the task stopped and the next step is the user’s. |
| Feature-detect | 'ariaNotify' in Element.prototype; where it is missing, do nothing. Support: Chrome and Edge 141, Firefox 150, Safari 27. |
Keyboard
| Key | Does |
|---|---|
| Tab · Shift+Tab | One stop for each native control, in DOM order, and one for a group of radios. Never a tabindex above 0, and no trap |
| Enter | Submits from a field. In a textarea it is a new line |
| Space | Toggles a checkbox, presses Show password, opens the help |
| Arrow keys | Choose in a group of radios, move in a select, step a number, change a segment of a date |
| Home · End · Page Up · Page Down | Move a slider |
Roles and properties
What the browser exposes, as the tests read it. Nothing here was heard in a screen reader yet.
| Part | Exposed |
|---|---|
| Form | A form landmark named by its heading, when it has one |
| Label | The name of the control, with “(required)” or “(optional)” in it, so the name contains the visible words (2.5.3) |
| Message | The description of the control, through aria-describedby: the hint, then the state |
| Control | Its state: aria-invalid is true or false, never absent |
| Group | A group named by its legend; every option carries aria-invalid and the same description |
| Error list | A heading that takes focus, and links named “Field: message” |
| Review and result | A heading described by its “Success: …” line, so both are read once |
| Show password | A button that keeps its name and says its state with aria-pressed |
Support
Yes is run in the browser tests of the component; No is not run here.
| Browser | Checked |
|---|---|
| Chromium | Yes |
| WebKit (Safari) | No |
| Firefox | No |
WCAG criteria
What each success criterion asks of a form, and what meets it.
| Success criterion | Level | Met by |
|---|---|---|
| 1.3.1 Info and Relationships | A | label for, fieldset + legend, aria-describedby to one message |
| 1.3.2 Meaningful Sequence | A | DOM order = visual order; message above the control |
| 1.3.5 Identify Input Purpose | AA | autocomplete tokens on personal data |
| 1.4.1 Use of Color | A | “Error:”, “Correct.” words, bar and border width |
| 1.4.3 Contrast (Minimum) | AA | Red #c8102e 5.9:1, green #1a7f37 5.1:1 |
| 1.4.10 Reflow | AA | One column at 320 px |
| 1.4.11 Non-text Contrast | AA | Control outlines and focus ring 3:1 |
| 2.1.1 Keyboard | A | Native controls, Enter submits |
| 2.1.2 No Keyboard Trap | A | No trap; dialogs close with Escape |
| 2.2.1 Timing Adjustable | A | Time limit: warn, let extend, keep the answers |
| 2.4.3 Focus Order | A | Submit: field or list heading; links focus fields |
| 2.4.6 Headings and Labels | AA | Labels say what to enter; submit named for the action |
| 2.4.7 Focus Visible | AA | The site’s ring on every control |
| 2.4.11 Focus Not Obscured (Minimum) | AA | No toast or sticky bar over the form |
| 2.5.3 Label in Name | A | “(required)” in the label text; Change buttons named by question |
| 2.5.7 Dragging Movements | AA | File drop zone: a real type="file" first |
| 2.5.8 Target Size (Minimum) | AA | 24 × 24 for options and links |
| 3.2.2 On Input | A | Judging never moves focus or changes the page |
| 3.3.1 Error Identification | A | Error text names the field and what is wrong |
| 3.3.2 Labels or Instructions | A | Format and limits in the hint before the mistake |
| 3.3.3 Error Suggestion | AA | The fix: “Try Monday, 23 November.” (not when it leaks) |
| 3.3.4 Error Prevention (Legal, Financial, Data) | AA | Review step, Confirm, cancel after confirming |
| 3.3.5 Help | AAA | details beside hard fields |
| 3.3.7 Redundant Entry | A | Never ask twice |
| 3.3.8 Accessible Authentication (Minimum) | AA | Paste and password managers allowed |
| 4.1.2 Name, Role, Value | A | Native controls with visible labels; Show is a button with aria-pressed |
| 4.1.3 Status Messages | AA | The words are in the visible message; field.ariaNotify() speaks them without moving focus. Nothing depends on the call being heard |
Related
This section is still in progress.
References
This section is still in progress.