Acme Design System

Accessibility Checklist

WCAG 2.1 AA requirements per component.

⚠
WCAG 2.1 AA is the minimum bar for all shipped components. AAA is the target where achievable.

Requirements for All Components

  • Color is never the only means of conveying information
  • All interactive elements reachable by keyboard (Tab, Shift+Tab)
  • Focus indicator always visible — never outline: none without a custom replacement
  • Touch targets minimum 44×44px on mobile
  • Text contrast ≥ 4.5:1 (normal text), ≥ 3:1 (large text ≥18pt or 14pt bold)
  • Non-text UI contrast ≥ 3:1 (borders, icons, focus rings)
  • All images have descriptive alt text or alt="" if purely decorative
  • Page has a logical heading hierarchy (H1 → H2 → H3)

Contrast Ratio Reference

CombinationForegroundBackgroundRatioResult
Primary button text #FFFFFF #1955A6 4.8:1 ✓ Pass
Primary button hover #FFFFFF #0B2F5C 9.2:1 ✓ Pass
Body text #383838 #FFFFFF 9.7:1 ✓ Pass
Helper text #909090 #FFFFFF 3.7:1 ⚠ Note
Error text #C62828 #FFFFFF 5.5:1 ✓ Pass
Dark navbar text #FFFFFF #0B2F5C 9.2:1 ✓ Pass
Brand bg text #1955A6 #E7EFF7 4.2:1 ✓ Pass
Disabled text #B0B0B0 #FFFFFF 2.1:1 ⚠ Note
⚠
Helper text and disabled text intentionally fall below 4.5:1 — they are supplemental and non-interactive. This is a deliberate system decision, not a bug.

Button

  • Contrast ≥ 4.5:1 all states — Primary: white on #1955A6 = 4.8:1 ✓
  • Focus ring: 3px #A8C4E4 outside stroke always visible
  • Disabled: use aria-disabled='true' not disabled attr (stays keyboard-focusable)
  • Icon-only buttons: aria-label required
  • Non-button element: add role='button' + keyboard handler
Visual Example

Input

  • Every input has a visible <label> — never use placeholder as label
  • Error state: aria-invalid='true' + aria-describedby → error message
  • Helper text: linked via aria-describedby
  • Required fields: aria-required='true' + visual indicator (not color alone)
  • Focus ring: 2px brand-primary border on :focus-visible
Visual Example
We'll never share your email.
Password must be at least 8 characters.

Checkbox

  • Native <input type='checkbox'> or role='checkbox' + aria-checked
  • Indeterminate state: aria-checked='mixed'
  • Group: wrap in <fieldset> with <legend>
  • Click target minimum 24×24px
Visual Example
Notification preferences

Radio

  • Group wrapped in <fieldset> with <legend> describing the question
  • Use native <input type='radio'> inside the group
  • Arrow keys navigate within group — native browser behavior
  • Group error: aria-invalid on the fieldset
Visual Example
Billing cycle

Toggle

  • role='switch' + aria-checked='true/false'
  • Visible label always present alongside toggle
  • State announced on change via live region or updated aria-label
  • Knob vs track contrast ≥ 3:1
Visual Example
Dark mode On
Email notifications Off

TabBar

  • role='tablist' on container; role='tab' on each tab; role='tabpanel' on content
  • Active tab: aria-selected='true'; all others aria-selected='false'
  • Tab panel: aria-labelledby pointing to its tab id
  • Arrow keys navigate tabs (left/right within tablist)
  • Disabled tab: aria-disabled='true'
Visual Example
Tab panel content for Dashboard

Alert

  • role='alert' for error/warning (live region, assertive announce)
  • role='status' for info/success (live region, polite announce)
  • Dismiss button: aria-label='Dismiss alert'
  • Icon is decorative: aria-hidden='true'
Visual Example
Error: Could not save changes. Please try again.
Success: Your changes have been saved.
Warning: You're approaching your storage limit.

Badge

  • Decorative badges: aria-hidden='true'
  • Meaningful (counts, status): provide text alternative in context
  • Count badges in tabs: tab label should include count, e.g. 'All (128)'
Visual Example
Active Complete Failed Warning Inactive

DataTable

  • Use <table> element with <th scope='col'> for column headers
  • Sortable columns: aria-sort='ascending / descending / none'
  • Table caption or aria-label on the <table>
  • Loading state: aria-busy='true' on the table
  • Empty state: descriptive message in <tbody>
  • Row actions: aria-label includes row context
Visual Example
Name ↑ Status Date
Acme RebrandActiveMar 10
Mobile AppReviewFeb 28

Avatar

  • Decorative: aria-hidden='true' on the entire element
  • Interactive (links to profile): aria-label='View [Name]'s profile'
  • Text initials: aria-hidden='true' on the text element
Visual Example
SK

Card

  • Interactive cards: wrap in <a> or <button>
  • Focus ring visible on interactive card wrapper
  • Images inside: meaningful alt text or alt='' if decorative
Visual Example
Project
Acme Rebrand
Brand identity refresh across all touchpoints.
Active