Accessibility
View SourceSutra UI is built with accessibility as a core principle. All components follow WAI-ARIA patterns and support keyboard navigation.
WCAG 2.1 AA Compliance
Sutra UI components are designed to meet WCAG 2.1 Level AA standards:
- Perceivable - Text alternatives, adaptable content, distinguishable colors
- Operable - Keyboard accessible, enough time, navigable
- Understandable - Readable, predictable, input assistance
- Robust - Compatible with assistive technologies
Keyboard Navigation
Global Patterns
| Key | Action |
|---|---|
Tab | Move focus to next focusable element |
Shift + Tab | Move focus to previous focusable element |
Enter / Space | Activate focused element |
Escape | Close modal, popover, dropdown |
Component-Specific Shortcuts
Tabs
| Key | Action |
|---|---|
Arrow Left/Right | Navigate between tabs |
Home | Go to first tab |
End | Go to last tab |
Dropdown Menu
| Key | Action |
|---|---|
Arrow Up/Down | Navigate menu items |
Enter | Select item |
Escape | Close menu |
Select
| Key | Action |
|---|---|
Arrow Up/Down | Navigate options |
Enter | Select option |
Escape | Close dropdown |
Type | Jump to matching option |
Accordion
| Key | Action |
|---|---|
Arrow Up/Down | Navigate accordion items |
Enter / Space | Toggle accordion panel |
Home | Go to first item |
End | Go to last item |
Dialog
| Key | Action |
|---|---|
Escape | Close dialog |
Tab | Cycle through focusable elements (trapped) |
Command Palette
| Key | Action |
|---|---|
Arrow Up/Down | Navigate results |
Enter | Select item |
Escape | Close palette |
ARIA Attributes by Component
Buttons
<.button>Save</.button>
<!-- Renders with proper button semantics -->
<.button size="icon" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</.button>
<!-- Icon buttons MUST have aria-label -->
<.button loading>Saving...</.button>
<!-- Sets aria-busy="true" when loading -->Form Controls
<.field>
<:label>Email</:label>
<.input type="email" name="email" />
<:error>Invalid email</:error>
</.field>
<!-- Automatically links label, input, and error with aria-describedby -->Dialog
<.dialog id="confirm">
<:title>Confirm Action</:title>
<:description>Are you sure?</:description>
Content here
</.dialog>
<!-- Sets aria-labelledby and aria-describedby automatically -->Tabs
<.tabs id="settings" default_value="account">
<:tab value="account">Account</:tab>
<:panel value="account">Account settings</:panel>
</.tabs>
<!-- Full tablist/tab/tabpanel ARIA pattern -->Focus Management
Focus Trapping
Modal components like dialog and command automatically trap focus:
- Focus moves to the dialog when opened
- Tab cycles through focusable elements within the dialog
- Focus returns to the trigger element when closed
Focus Indicators
All interactive elements have visible focus indicators using the --ring CSS variable:
:root {
--ring: oklch(0.705 0.015 286.067); /* Focus ring color */
}Focus indicators are:
- Always visible (never
outline: none) - High contrast against backgrounds
- Consistent across all components
Skip Links
For page-level accessibility, add a skip link at the top of your layout:
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-background focus:border focus:rounded">
Skip to main content
</a>
<main id="main-content">
<!-- Page content -->
</main>Screen Reader Support
Live Regions
Toast notifications use aria-live to announce messages:
<.toaster />
<!-- Creates a live region for announcements -->
<.toast>File saved successfully</.toast>
<!-- Announced by screen readers -->Semantic HTML
Sutra UI uses semantic HTML elements:
<button>for buttons (not<div>)<dialog>for modals<table>for data tables<nav>for navigation<form>for forms
Hidden Content
Use these utilities for screen reader content:
<!-- Visually hidden but accessible to screen readers -->
<span class="sr-only">Additional context</span>
<!-- Hidden from screen readers -->
<span aria-hidden="true">Decorative icon</span>Testing Accessibility
Recommended Tools
- axe DevTools - Browser extension for automated testing
- WAVE - Web accessibility evaluation tool
- VoiceOver (macOS) / NVDA (Windows) - Screen reader testing
- Keyboard only - Navigate without a mouse
Testing Checklist
- [ ] All interactive elements are focusable with Tab
- [ ] Focus order is logical
- [ ] Focus indicators are visible
- [ ] All images have alt text
- [ ] Form inputs have labels
- [ ] Error messages are associated with inputs
- [ ] Color is not the only means of conveying information
- [ ] Text has sufficient contrast (4.5:1 for normal, 3:1 for large)
Common Accessibility Patterns
Icon Buttons
Always provide an aria-label:
<!-- Good -->
<.button size="icon" aria-label="Delete item">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
</.button>
<!-- Bad - no accessible name -->
<.button size="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
</.button>Loading States
Use aria-busy and announce loading:
<.button loading aria-busy="true">
<.spinner class="mr-2" />
Loading...
</.button>Disabled States
Use the disabled attribute, not just styling:
<!-- Good -->
<.button disabled>Cannot submit</.button>
<!-- Bad - looks disabled but isn't -->
<.button class="opacity-50 cursor-not-allowed">Cannot submit</.button>Form Validation
Associate errors with inputs:
<.field>
<:label>Password</:label>
<.input
type="password"
name="password"
aria-invalid={@errors != []}
/>
<:error :for={error <- @errors}>{error}</:error>
</.field>Next Steps
- Components Cheatsheet - All components with examples
- Installation Guide - Setup instructions
- Theming Guide - Customize your theme