UX Design

Accessible Design System Components: Building Inclusion at the Foundation

By EZUD Published · Updated

Accessible Design System Components: Building Inclusion at the Foundation

A design system is where accessibility either scales or fails. When individual teams build components from scratch, accessibility depends on each developer’s knowledge and prioritization. When a design system provides accessible components as the default, every product built on it inherits that accessibility. The design system is the highest-leverage point for inclusive design in an organization.

Why Design Systems Are the Leverage Point

Consider a simple button component. If the design system’s <Button> component enforces:

  • Proper <button> element semantics (not a styled <div>)
  • Minimum 44x44 CSS pixel touch target
  • Visible focus indicator
  • Sufficient color contrast for all variants
  • Loading state with screen reader announcement
  • Disabled state with proper aria-disabled handling

Then every team that uses the button gets all of this for free. Multiply this across dozens of components — modals, tabs, forms, navigation, data tables — and the design system becomes the accessibility engine for the entire organization.

Core Accessible Component Patterns

Button

The most fundamental component. Requirements:

  • Uses <button> element (or <a> for navigation actions)
  • Icon-only buttons have aria-label
  • Loading buttons communicate state: aria-busy="true" and visible spinner with screen reader text
  • Disabled buttons use aria-disabled="true" rather than the disabled attribute when the disabled reason needs to be communicated (disabled elements are not focusable, making it hard to discover why they are disabled)
  • All variants (primary, secondary, danger) meet WCAG contrast ratios

Form Controls

Text inputs, selects, checkboxes, radios, and textareas. See the W3C WAI-ARIA Authoring Practices for each:

  • Every input has a visible label (not just placeholder text)
  • Required fields use aria-required="true" and a visible indicator
  • Error states use aria-invalid="true" with aria-describedby pointing to the error message
  • Descriptions and hints use aria-describedby
  • Custom selects follow the listbox or combobox pattern
  • Form validation errors are announced to screen readers

Following the accessible modal pattern:

  • role="dialog" or native <dialog> element
  • aria-labelledby and aria-describedby
  • Focus trap on open
  • Focus return on close
  • Escape key dismissal
  • Background inertness

Tabs

Following the tab pattern:

  • role="tablist", role="tab", role="tabpanel"
  • Roving tabindex
  • Arrow key navigation
  • aria-selected state management

Accordion

Following the accordion pattern:

  • Button triggers inside headings
  • aria-expanded state
  • aria-controls linking
  • Tab-key navigation between headers

Data Table

Following the table pattern:

  • Semantic <table>, <th>, <caption>
  • scope attributes on headers
  • Sortable columns with aria-sort
  • Responsive behavior that preserves accessibility

Tooltip

Following the tooltip pattern:

  • role="tooltip" for text-only tooltips
  • aria-describedby linking
  • Hover and focus activation
  • Escape dismissal

Design System Documentation Standards

Component documentation should include accessibility information as a first-class section, not an afterthought appendix:

For Each Component, Document:

ARIA roles and attributes: What ARIA is applied automatically and what the consumer must configure (e.g., aria-label for icon buttons).

Keyboard interaction: A table of keyboard behaviors:

KeyAction
TabMoves focus to the component
Enter/SpaceActivates the button
EscapeCloses the dialog

Screen reader announcements: What a screen reader user hears when interacting with the component. Include example announcements for major screen readers.

Do/Don’t guidance: Common misuse patterns that break accessibility:

  • Do: Use the Button component for actions. Don’t: Use a styled <div> with an onClick handler.
  • Do: Provide a visible label for every form field. Don’t: Use placeholder text as the label.

Testing checklist: Component-specific tests developers should run:

  • Keyboard focus is visible
  • Tab order is logical
  • Screen reader announces role and state
  • Color contrast meets WCAG AA

Enforcing Accessibility in the Component API

Design the component API to make inaccessible usage difficult:

Required Accessibility Props

// TypeScript: label is required, forcing accessible usage
interface ButtonProps {
  children: React.ReactNode;
  onClick: () => void;
  /** Required for icon-only buttons */
  'aria-label'?: string;
}

// Validation: warn if neither children text nor aria-label is provided

Accessible Defaults

  • Focus indicators enabled by default (never outline: none in the base component)
  • Color contrast validated at the token level — design tokens that fail contrast ratios are flagged during design review
  • Motion respects prefers-reduced-motion by default in all animated components, following motion accessibility principles

Linting and Validation

  • ESLint with eslint-plugin-jsx-a11y catches common issues in React
  • Storybook with @storybook/addon-a11y runs axe on every component story
  • CI pipeline runs axe-core against component documentation pages

Design Tokens and Accessibility

Color Tokens

Define color token pairs with contrast ratios baked in:

{
  "color": {
    "text": {
      "primary": { "value": "#1a1a1a", "contrast-on-white": "16.6:1" },
      "secondary": { "value": "#595959", "contrast-on-white": "7.0:1" },
      "disabled": { "value": "#999999", "contrast-on-white": "2.8:1", "note": "Below AA — use only with additional indicators" }
    }
  }
}

Document which token pairs meet which WCAG levels. Flag combinations that fail and provide guidance on when they can and cannot be used.

Spacing Tokens

Define minimum touch target sizes as tokens:

{
  "spacing": {
    "touch-target-minimum": { "value": "44px", "note": "WCAG 2.5.8 Target Size minimum" }
  }
}

Typography Tokens

  • Minimum body text size: 16px (prevents mobile browser zoom on focus)
  • Line height minimum: 1.5 for body text (WCAG 1.4.12)
  • Paragraph spacing: at least 2x the font size (WCAG 1.4.12)

Component Testing Strategy

Automated Testing

Run axe-core on every component variant in every state:

  • Default, hover, focus, active, disabled, error, loading
  • With and without optional props (labels, descriptions)
  • At different viewport sizes

Unit Tests for Keyboard

Write unit tests for keyboard interaction:

it('opens dropdown on Enter key', () => {
  render(<Select options={options} />);
  const trigger = screen.getByRole('combobox');
  fireEvent.keyDown(trigger, { key: 'Enter' });
  expect(screen.getByRole('listbox')).toBeVisible();
});

Screen Reader Testing Matrix

Test critical components across:

  • NVDA + Chrome (Windows)
  • JAWS + Edge (Windows)
  • VoiceOver + Safari (macOS)
  • VoiceOver + Safari (iOS)
  • TalkBack + Chrome (Android)

Document results in a compatibility matrix and address inconsistencies.

Contribution Guidelines

When accepting new components or modifications:

  1. Accessibility review is required — no component merges without passing automated checks and manual keyboard/screen reader testing
  2. ARIA pattern reference — new interactive components must reference the corresponding W3C WAI-ARIA Authoring Practices pattern
  3. Documentation requirement — accessibility section in component docs is mandatory, not optional
  4. Testing evidence — PR must include evidence of testing with at least one screen reader

Key Takeaways

A design system is where accessibility scales. By building ARIA patterns, keyboard interactions, contrast ratios, and focus management into shared components, every product inherits accessibility by default. Document the keyboard model and screen reader behavior for every component. Enforce accessibility through API design, linting, and review requirements. Test across the screen reader matrix. When the design system is accessible, the organization’s products are accessible — this is the most efficient path to inclusive digital experiences.

Sources