Skip to content

DOMAIN:ACCESSIBILITY:COMPONENT_PATTERNS

OWNER: julian (compliance), alexander (design) ALSO_USED_BY: floris, floor, antje UPDATED: 2026-03-26 SCOPE: accessible implementation patterns for common UI components REFERENCE: WAI-ARIA Authoring Practices Guide (APG) — w3.org/WAI/ARIA/apg/patterns/


COMPONENT_PATTERNS:PRINCIPLE

RULE_1: use native HTML elements before ARIA — no ARIA is better than bad ARIA RULE_2: every custom component must match the WAI-ARIA APG keyboard interaction pattern RULE_3: every custom component must be tested with NVDA and VoiceOver RULE_4: component implementations must be documented in DESIGN.md with accessibility notes


PATTERN:FORMS

FORM_FIELD_BASICS

RULE: every input must have a visible, associated label RULE: never use placeholder as the only label — it disappears on input RULE: group related fields with fieldset + legend RULE: mark required fields with aria-required="true" AND visual indicator RULE: describe constraints with aria-describedby

PATTERN — TEXT_INPUT:

<div class="field">
  <label for="email">Email address <span aria-hidden="true">*</span></label>
  <input type="email" id="email" name="email"
         autocomplete="email"
         aria-required="true"
         aria-describedby="email-hint email-error">
  <p id="email-hint" class="hint">We will use this for login</p>
  <p id="email-error" class="error" role="alert" hidden>
    Please enter a valid email address
  </p>
</div>

PATTERN — FIELD_GROUP:

<fieldset>
  <legend>Billing address</legend>
  <label for="street">Street</label>
  <input type="text" id="street" autocomplete="street-address">
  <label for="city">City</label>
  <input type="text" id="city" autocomplete="address-level2">
  <label for="postal">Postal code</label>
  <input type="text" id="postal" autocomplete="postal-code">
</fieldset>

FORM_VALIDATION

RULE: display errors in text, not color alone RULE: associate errors with fields via aria-describedby RULE: use aria-invalid="true" on invalid fields RULE: announce errors with role="alert" or aria-live="assertive" RULE: move focus to first error field on form submission failure RULE: provide error summary at top of form for complex forms

PATTERN — ERROR_STATE:

<label for="phone">Phone number</label>
<input type="tel" id="phone"
       aria-invalid="true"
       aria-describedby="phone-error">
<p id="phone-error" class="error" role="alert">
  Phone number must include country code (e.g., +31 6 12345678)
</p>

PATTERN — ERROR_SUMMARY:

<div role="alert" aria-labelledby="error-heading" tabindex="-1">
  <h2 id="error-heading">There are 2 errors in this form</h2>
  <ul>
    <li><a href="#email">Email address is required</a></li>
    <li><a href="#phone">Phone number format is invalid</a></li>
  </ul>
</div>

SELECT / COMBOBOX

NATIVE_SELECT: use — best screen reader support, keyboard built-in FALLBACK: text input with format hint if native not suitable CUSTOM: only if client requires specific design — follow WAI-ARIA dialog date picker pattern

PATTERN — NATIVE:

<label for="start-date">Start date</label>
<input type="date" id="start-date" name="start-date"
       min="2026-01-01" max="2027-12-31"
       aria-describedby="date-format">
<p id="date-format" class="hint">Format: dd-mm-yyyy</p>

PATTERN — TEXT_FALLBACK:

<label for="start-date">Start date</label>
<input type="text" id="start-date" name="start-date"
       placeholder="dd-mm-yyyy"
       pattern="\d{2}-\d{2}-\d{4}"
       aria-describedby="date-hint">
<p id="date-hint" class="hint">Enter date as dd-mm-yyyy (e.g., 26-03-2026)</p>

CUSTOM_DATE_PICKER_REQUIREMENTS (if unavoidable): - dialog pattern with role="dialog" - grid pattern for calendar with role="grid" - arrow keys navigate days, PageUp/Down navigate months - Enter selects date and closes dialog - Escape closes dialog without selecting - focus returns to trigger button on close - always provide text input as alternative to calendar widget


COMPONENT_PATTERNS:AGENT_INSTRUCTIONS

FOR floris, floor: - follow these patterns as baseline for all component implementations - if shadcn/ui component matches, verify it meets these ARIA requirements before using - if it does not meet requirements, wrap or extend — do not skip accessibility - custom components MUST be tested with keyboard + NVDA before PR merge

FOR alexander: - reference these patterns in DESIGN.md when specifying component behavior - design focus indicators for every interactive component - specify touch target sizes (minimum 24x24 CSS pixels, recommended 44x44)

FOR antje: - use the keyboard interaction tables to verify correct behavior - test screen reader announcements match expected pattern - verify ARIA attributes are correct using browser accessibility tree

FOR julian: - include component-level conformance notes in audit reports - flag custom components that deviate from APG patterns


READ_ALSO: domains/accessibility/index.md, domains/accessibility/wcag-2-2.md, domains/accessibility/testing-methodology.md, domains/accessibility/pitfalls.md