diff --git a/tutorials/html/08-forms/accessible-forms.mdx b/tutorials/html/08-forms/accessible-forms.mdx index ab2a288b0..eade1946d 100644 --- a/tutorials/html/08-forms/accessible-forms.mdx +++ b/tutorials/html/08-forms/accessible-forms.mdx @@ -1,16 +1,566 @@ --- -id: accessible-forms -title: "Best Practices for Accessible HTML Forms" +id: html-accessible-forms +title: "Accessible HTML Forms" sidebar_label: "Accessible Forms" -sidebar_position: 20 +sidebar_position: 21 slug: /html/forms/accessible-forms -description: "Ensure forms comply with WCAG standards using proper labeling, explicit field grouping, clear error messages, and ARIA attributes." +description: "Learn how to build accessible HTML forms: visible labels, grouping with fieldset and legend, hints and instructions, marking required fields, clear error messages with aria-invalid and aria-describedby, autocomplete, keyboard and focus, touch target size, WCAG 2.2 rules for forms, redundant entry and accessible authentication, and a practical testing checklist." keywords: - accessible HTML forms - - WCAG form accessibility - - aria-describedby form errors - - screen reader form navigation + - form accessibility + - WCAG forms + - label for input + - fieldset legend accessibility + - aria-describedby aria-invalid + - accessible form errors + - placeholder is not a label + - WCAG 2.2 target size + - form accessibility checklist + - learn HTML forms - CodeHarborHub HTML -authors: [ajay-dhangar] -tags: [html, forms, accessibility, a11y] ---- \ No newline at end of file +tags: [html, web-development, beginner, forms, accessibility, wcag, a11y, frontend] +--- + +import {useState, useRef} from 'react'; + +export function audit(form) { + const issues = []; + const fields = Array.from(form.elements).filter( + (el) => ["INPUT", "SELECT", "TEXTAREA"].includes(el.tagName) && !["hidden", "submit", "button", "reset"].includes(el.type) + ); + fields.forEach((el) => { + const label = el.name || el.type; + const hasName = + (el.labels && el.labels.length > 0) || + el.getAttribute("aria-label") || + el.getAttribute("aria-labelledby"); + if (!hasName) { + issues.push("The " + label + " field has no label. Screen readers cannot tell what it is for." + (el.placeholder ? " (A placeholder is not a label.)" : "")); + } + const wantsAutocomplete = ["name", "email", "tel", "username"].includes(el.name) || ["email", "tel"].includes(el.type); + if (wantsAutocomplete && !el.getAttribute("autocomplete")) { + issues.push("The " + label + " field is about the visitor but has no autocomplete token."); + } + }); + return {count: fields.length, issues}; +} + +export function AuditDemo() { + const poorRef = useRef(null); + const goodRef = useRef(null); + const [report, setReport] = useState(null); + return ( +
+
+
event.preventDefault()} style={{flex: "1 1 14rem"}}> +

Form A

+
+
+
+ +
+
event.preventDefault()} style={{flex: "1 1 14rem"}}> +

Form B

+
+
+
+
+
+
+ +
+
+

+ +

+ {report ? ( +
+ {[["Form A", report.a], ["Form B", report.b]].map(([title, result]) => ( +
+ + {title}: {result.issues.length === 0 ? "no problems found in " + result.count + " fields" : result.issues.length + " problems in " + result.count + " fields"} + +
    + {result.issues.map((text) => (
  • {text}
  • ))} +
+
+ ))} +
+ ) : null} +
+ ); +} + +export function GroupedDemo() { + const [value, setValue] = useState(""); + return ( +
event.preventDefault()}> +
+ How should we contact you? (required) +

We will only use this to answer your message.

+ + + +
+ +
+ ); +} + +Think about the last time you filled in a form. You **saw** the labels, **clicked** the box you wanted, **read** the red error message, and **moved** your mouse to the button. Now imagine doing it **without seeing the screen**, or **without using a mouse**, or with a tremor, or while holding a baby in one arm, or in bright sunlight on a small phone. + +That is the point of **accessible forms**: forms that work for **everyone**, in every situation. And here is the good news: most of it comes **free** from the HTML elements you have already learned. This lesson pulls it all together into one clear set of habits. + + +
+ +## What You Will Learn + +- Why accessible forms matter, and who benefits +- The seven habits that fix most problems +- Labels, groups, hints, and required markers +- Clear errors that everyone can find and fix +- Keyboard, focus, and touch targets +- The key WCAG 2.2 rules for forms +- Easy authentication and less retyping +- A checklist to test any form + +## Why It Matters + +Accessible forms help far more people than you might think: + +| Who | How accessible forms help | +| :-- | :-- | +| Screen reader users | Hear each field's label, hints, and errors | +| Keyboard-only users (including voice control and switch users) | Reach and operate every field without a mouse | +| People with low vision | Can zoom in, and see clear focus and good contrast | +| People with motor difficulties | Have big targets and less typing (autofill) | +| People with dyslexia or cognitive differences | Get plain words, clear instructions, and forgiving errors | +| People on phones, in sunlight, or in a hurry | Get the right keyboard, big buttons, and clear messages | +| Everyone, with a temporary injury or a noisy room | Gets a form that just works | + +It is also often a **legal requirement** (many countries reference WCAG, the Web Content Accessibility Guidelines, in their laws), and accessible forms usually have **higher completion rates**, which means more sign-ups and sales. + +## The Seven Habits + +If you remember only seven things, remember these: + +1. **Every field has a visible, connected label.** +2. **Group related fields** with `fieldset` and `legend`. +3. **Use the right element and input type.** Native controls first. +4. **Say what is required, and what format is expected,** before the visitor types. +5. **Write clear errors,** linked to their fields, and move focus to the first problem. +6. **Make everything work with a keyboard,** with a clearly visible focus. +7. **Test it** with a keyboard and a screen reader, not only your eyes. + +## Try It: A Tiny Accessibility Audit + +Below are two forms that look almost the same. Press the button, and a small script inspects each field, using the same information a screen reader sees: is there a label, and is there an `autocomplete` token? + + + + +```html title="index.html" + +
+ + + + +
+ + +
+ + + + + + + + + + +
+``` + +```js title="script.js" +function audit(form) { + for (const el of form.elements) { + if (!["INPUT", "SELECT", "TEXTAREA"].includes(el.tagName)) continue; + if (["hidden", "submit", "button", "reset"].includes(el.type)) continue; + + const hasName = + el.labels.length > 0 || + el.getAttribute("aria-label") || + el.getAttribute("aria-labelledby"); + + if (!hasName) console.log(el.name + ": no label!"); + } +} +``` + +
+ + + +<> + + + + + +
+ +Form A has placeholders only. Sighted visitors can read them, but the moment they start typing, the hint vanishes, and screen readers may not announce a placeholder as a name at all. This tiny check catches only a small slice of problems, but it shows the key idea: **accessibility is built into the markup.** + +## 1. Labels: the Foundation + +Every field needs a **visible** `