Skip to content

Best Practices: Accessibility

When to Use

Every AJAX implementation must meet WCAG 2.1 Level AA standards.

Accessibility Requirements

Accessibility Requirements:

  1. Screen Reader Announcements
  2. Announce all content updates using AnnounceCommand or MessageCommand
  3. Use 'polite' priority for non-critical updates
  4. Use 'assertive' only for errors requiring immediate attention
  5. Provide meaningful context ("Search results updated with 5 items" not "Updated")

  6. Keyboard Navigation

  7. Add 'keypress' => TRUE to all AJAX buttons
  8. Manage focus after updates with FocusFirstCommand
  9. Ensure all triggers are keyboard-accessible (no click-only elements)
  10. Test with Tab, Enter, Space, Esc keys

  11. Focus Management

  12. Return focus to logical element after update
  13. Don't move focus unexpectedly (confuses users)
  14. Use FocusFirstCommand for new content regions
  15. Close dialogs with Esc key (built-in to dialog system)

  16. Loading Indicators

  17. Provide progress messages read by screen readers
  18. Show visual loading states (spinners, progress bars)
  19. Disable triggering element during processing (prevent double-submit)
  20. Clear loading state after completion

  21. ARIA Attributes

  22. Use aria-live="polite" for dynamic regions
  23. Use aria-atomic="true" to read entire updated region
  24. Add aria-busy="true" during loading
  25. Mark expanded/collapsed states with aria-expanded

Accessibility Testing Checklist

Accessibility Testing Checklist:

  • [ ] Unplug mouse, navigate entire workflow with keyboard only
  • [ ] Test with NVDA (Windows), JAWS (Windows), or VoiceOver (Mac)
  • [ ] Verify all AJAX triggers are keyboard-accessible
  • [ ] Confirm screen reader announces all content changes
  • [ ] Check focus doesn't get lost after updates
  • [ ] Verify loading indicators are announced
  • [ ] Test with browser zoom at 200%
  • [ ] Run automated tests with axe DevTools or WAVE

Pattern

// Screen reader announcements
$response->addCommand(new AnnounceCommand('Results updated with 5 items', 'polite'));

// Keyboard trigger
$form['trigger']['#ajax']['keypress'] = TRUE;

// Focus management
$response->addCommand(new FocusFirstCommand('#new-content-region'));

// ARIA attributes on dynamic regions
$form['results'] = [
  '#type' => 'container',
  '#attributes' => [
    'id' => 'results',
    'aria-live' => 'polite',
    'aria-atomic' => 'true',
    'aria-busy' => 'false',  // Set to 'true' during loading via InvokeCommand
  ],
];

// Loading indicator with accessible message
$form['trigger']['#ajax']['progress'] = [
  'type' => 'throbber',
  'message' => t('Loading, please wait...'),
];

See Also