13 KiB
Handling states, validation, and accessibility
Role
You are implementing how the application responds to user actions and ensuring page-level accessibility. These patterns determine whether users trust the application. Follow them for every form, API call, and user action.
Aura components handle many accessibility concerns automatically; you are responsible for composition, copy, focus, and page structure. Aura's focus system uses shadow-focus-ring (custom shadow token). Never remove or override it.
All UI elements use Aura components and tokens. Error states use the destructive token family, warnings use warning tokens, success uses success tokens.
For message wording patterns, see writing-copy.md.
For all Storybook URLs, see ./storybook-links.md.
| Concern | Aura handles | You verify |
|---|---|---|
| Focus indicators | shadow-focus-ring on interactive elements | Not hidden by overflow or z-index |
| Keyboard activation | Button: Enter/Space. Input: standard keys | Custom elements also respond |
| ARIA roles | Correct roles on Dialog, Segmented Control (Tabs ARIA pattern), etc. | Custom components have roles |
| Color contrast | Token pairs designed for AA compliance | Page backgrounds don't reduce contrast |
| Dark mode | Semantic tokens adapt automatically | Custom colors also work in dark mode |
| Disabled states | Communicated via aria-disabled | Reason for disabled is accessible |
| Focus trapping | Dialog traps focus when open | You return focus to trigger on close |
See writing-copy.md for full message patterns.
| Field type | required | format | length | range | uniqueness |
|---|---|---|---|---|---|
| Text Input | yes | — | optional | — | optional |
| Email Input | yes | yes | — | — | optional |
| Password | yes | yes | yes | — | — |
| Number Input | yes | — | — | yes | — |
| Date Picker | yes | — | — | yes | — |
| Textarea | yes | — | yes | — | — |
| Select | yes | — | — | — | — |
| Combobox | yes | — | — | — | — |
| Checkbox | — | — | — | — | — |
| File Upload | yes | yes | — | yes (size) | — |
"yes" = must implement. "optional" = implement if relevant.
A complete form field with all states (default, focused, error, success, disabled):import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { HelperText } from '@/components/ui/helper-text';
{/* Default / Focused state */}
Key implementation details:
- Label with required indicator (asterisk in text-destructive)
- Input with aria-describedby linking to HelperText
- Input with aria-invalid reflecting error state
- Validation on blur via onBlur handler
- HelperText swaps between hint (default) and error message
- Disabled state during form submission
{/* Skeleton while page loads */} {isLoading ? (
{/* Blank screen while loading */} {isLoading ? null : }
Every API failure must show a user-facing message using Aura Alert component. Never fail silently. See `writing-copy.md` for message wording. import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle } from 'lucide-react';{error && ( {error} Try again )}
Use Sonner toast for brief confirmations. import { toast } from 'sonner';// After save toast.success('Report saved successfully.');
// After delete toast.success('Report deleted.');
// After bulk action
toast.success(${count} items archived.);
// Vague toast.success('Done!');
Destructive actions must show Dialog with specific action verb. See `writing-copy.md` for copy. import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; Delete this report? This will permanently remove "{report.name}" and all associated data. This cannot be undone. setShowDelete(false)} > Cancel Delete report {/* Yes/No, no description, wrong variant */} Confirm Are you sure? No Yes - Tab order follows visual reading order - Every interactive element reachable via Tab - No keyboard traps - Skip-to-content link on pages with complex nav Skip to main contentReports
{/* Content in logical tab order */} Use heading levels (H1–H6) in strict sequential order. - One H1 per page (the page title) - Never skip levels (H1 directly to H3) - Never use heading tags for visual sizing — use text-* classes from the Typography foundation insteadAura applies text-undefined-foreground to headings by default.
Every image needs alt. Icons in buttons need aria-label. | Type | Approach | Example | |------|----------|---------| | Informational | Describe content | alt="Chart: output up 20%" | | Decorative | Empty | alt="" | | Icon button | aria-label on parent | aria-label="Delete report" | | Icon with label | Hide icon | aria-hidden="true" on icon | {/* Icon + text: hide icon from screen reader */} Delete report{/* Icon only: label on button */}
| Scenario | Method | |----------|--------| | Search results update | aria-live="polite" | | Form error | aria-live="assertive" | | Toast | Sonner handles this | | Dialog opens | Focus moves to dialog (Aura handles) | | Dialog closes | Return focus to trigger | {/* Screen reader announcement for filtered results */}For automated checks, use WAVE, axe DevTools, or Lighthouse in Chrome DevTools. For manual verification, unplug the mouse and complete primary tasks with keyboard only; spot-check with VoiceOver (Mac) or NVDA (Windows) for critical flows.
Before submitting any page: - [ ] Tab through all elements in logical order? - [ ] Every button/link works with Enter/Space? - [ ] Every dialog opens/closes with keyboard? - [ ] Escape closes dialogs, popovers, dropdowns? - [ ] Every image has appropriate alt text? - [ ] Every form field has a visible label? - [ ] Non-color indicator for every status? - [ ] Headings follow H1 → H2 → H3? - [ ] Dynamic updates announced to screen readers? - [ ] Focus ring (shadow-focus-ring) visible on all elements? **Forms and async** 1. Destructive action with undo? — Still confirm. Mention undo in body: "You can undo within 30 seconds." 2. Bulk delete? — One confirmation: "Delete 12 reports?" 3. Auto-save? — Subtle "Saved" indicator, not toast each time. 4. Error in multi-step flow? — Don't lose progress. Show error on current step. Let user retry.Accessibility
- Complex data viz? — Text summary via alt or sr-only text.
- Drag-and-drop? — Keyboard alternative required.
- Real-time dashboard? — aria-live="polite", not "assertive".
- Third-party embed? — iframe with descriptive title.