Files
md-files/.claude/skills/design/handling-states.md
T
2026-05-31 20:25:41 +00:00

13 KiB
Raw Blame History

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.

What Aura components handle automatically:
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
- Validate on blur, not on every keystroke - Show errors inline, adjacent to the field - Preserve user input on failure (never clear the form) - Move focus to first error field on submission failure - Announce errors to screen readers via aria-live | State | Background | Text | Border | |-------|-----------|------|--------| | Error | bg-destructive | text-destructive-foreground | border-destructive | | Warning | bg-warning | text-warning-foreground | — | | Success | bg-success | text-success-foreground | — | | Disabled | bg-disabled | text-disabled-foreground | — | | Field type | Correct message | Incorrect message | |-----------|----------------|-------------------| | Required | "Report name is required." | "Required" | | Email | "Email must include an @ symbol." | "Invalid" | | Password | "At least 8 characters." | "Too short" | | Number | "Value must be between 1 and 100." | "Invalid" | | Date | "End date must be after start date." | "Invalid date" |

See writing-copy.md for full message patterns.

Every form field must support the validation states applicable to its type. Use this table to determine which states to implement:
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 */}

Report name * setName(e.target.value)} onBlur={validateName} aria-describedby="report-name-helper" aria-invalid={!!nameError} disabled={isSubmitting} /> {nameError ? ( {nameError} ) : ( A descriptive name for your report. )}

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
Any action taking more than 300ms must show a loading indicator using Aura components. | Context | Pattern | Aura component | |---------|---------|---------------| | Page load | Skeleton screen | Skeleton | | Button action | Button disabled + spinner | Button loading state | | Data refresh | Overlay spinner | Spinner on existing content | | Long operation | Progress bar + message | Progress | {/* Button loading during async action */} {isSaving ? ( <> Saving... ) : ( 'Save changes' )}

{/* Skeleton while page loads */} {isLoading ? (

) : ( )} {/* No loading state */} Save changes

{/* 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.);

// No feedback await saveReport(data); navigate('/reports');

// 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 content

Reports

{/* Content in logical tab order */} Use heading levels (H1H6) 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 instead

Aura 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 */}
{results.length} results found for "{query}"
Never use color alone to convey meaning. {/* Status with text + color */} Active {/* Color only — invisible to colorblind users */} Short sentences and simple grammar translate more reliably. Plan for text expansion in localized UIs (e.g. German often adds 3040% length); allow flexible button and title widths.

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

  1. Complex data viz? — Text summary via alt or sr-only text.
  2. Drag-and-drop? — Keyboard alternative required.
  3. Real-time dashboard? — aria-live="polite", not "assertive".
  4. Third-party embed? — iframe with descriptive title.