Skip to content

Accessibility

Accessibility is not an add-on in DelightStack — it is built into every component from the start. Components use semantic HTML, proper ARIA attributes, keyboard navigation, and focus management by default.

Components render appropriate semantic elements:

  • Button renders <button> (not <div>)
  • Input uses <input> with proper type attributes and linked <label> elements
  • Modal and Alert use <dialog>
  • List renders <ul> / <ol> with <li> items
  • Tabs uses role="tablist", role="tab", and role="tabpanel"
  • Accordion uses semantic <details> / <summary>
  • Breadcrumbs renders a <nav> with aria-label="Breadcrumb"
  • Progress uses <progress> or role="progressbar" with aria-valuenow

Components set ARIA attributes automatically based on their state:

<!-- Alert automatically sets role="alertdialog" -->
<Alert title="Confirm" open>Are you sure?</Alert>
<!-- Input sets aria-invalid and aria-describedby for error messages -->
<Input label="Email" error="Please enter a valid email" />
<!-- Toggle sets role="switch" and aria-checked -->
<Toggle label="Notifications" value />
<!-- Expandable content uses aria-expanded and aria-controls -->
<Accordion>...</Accordion>

Components handle focus automatically where it matters:

  • Modal and Alert — Focus moves into the dialog when it opens and returns to the trigger element when it closes.
  • Drawer and BottomSheet — Same focus-on-open/restore-on-close behavior.
  • CommandPalette — Focus moves to the search input immediately on open.
  • Select — Focus moves to the dropdown list when opened and back to the trigger on selection.
  • Menu and ContextMenu — Focus moves to the first item when opened.

Overlay components trap focus to prevent tabbing out into the background content:

  • Modal — Tab and Shift+Tab cycle within the dialog.
  • Alert — Focus is confined to the confirmation actions.
  • Drawer — Focus stays inside the slide-out panel.
  • BottomSheet — Focus is trapped within the sheet.

Focus trapping is implemented via the focusTrap utility from @delightstack/utilities, which handles edge cases like dynamic content and nested focusable elements.

All component animations respect the prefers-reduced-motion media query. When a user has motion reduction enabled:

  • Transitions are shortened or removed entirely
  • Slide animations become instant reveals
  • Ripple effects on buttons are disabled
  • Confetti and counter animations are simplified
  • Carousel slides snap without sliding transitions

Components handle this internally — you do not need to add any reduced motion logic yourself.

KeyAction
EnterActivate the button
SpaceActivate the button
KeyAction
EscapeClose the overlay
TabCycle forward through focusable elements
Shift+TabCycle backward through focusable elements
KeyAction
EnterOpen dropdown / select item
SpaceOpen dropdown / select item
ArrowDownMove to next option
ArrowUpMove to previous option
HomeJump to first option
EndJump to last option
EscapeClose dropdown
Type-aheadJump to matching option
KeyAction
ArrowLeftPrevious tab
ArrowRightNext tab
HomeFirst tab
EndLast tab
KeyAction
EnterToggle section
SpaceToggle section
KeyAction
ArrowDownNext result
ArrowUpPrevious result
EnterExecute selected command
EscapeClose palette
KeyAction
ArrowDownNext visible node
ArrowUpPrevious visible node
ArrowRightExpand node / move to child
ArrowLeftCollapse node / move to parent
EnterSelect/activate node
HomeFirst node
EndLast visible node
KeyAction
ArrowDownNext row
ArrowUpPrevious row
EnterActivate row
KeyAction
ArrowRightIncrease value
ArrowLeftDecrease value
ArrowUpIncrease value
ArrowDownDecrease value
HomeMinimum value
EndMaximum value

The default design tokens are chosen to meet WCAG AA contrast requirements:

  • Body text (--color-text on --color-bg) exceeds 4.5:1 contrast ratio in both light and dark modes.
  • Muted text (--color-text-muted on --color-bg) meets 4.5:1 for normal-sized text.
  • Action text (--color-action-text on --color-action) is designed for legibility on colored backgrounds.
  • Feedback colors are chosen to be distinguishable for common forms of color vision deficiency.
  • Toast notifications use aria-live="polite" so screen readers announce them without interrupting the current task.
  • Progress uses aria-live="polite" to announce progress changes.
  • Form validation errors are announced via aria-describedby links from the input to the error message.
  • Confetti is marked as aria-hidden="true" since it is purely decorative.
  • Counter animations are invisible to screen readers — the final value is the only thing read.
  • Comparison slider provides a text description of the comparison for screen readers.
  • Input and Select automatically associate labels via id and for attributes.
  • Toggle and Checkbox render proper label associations.
  • Rating announces the current value (e.g., “3 out of 5 stars”).
  • Pagination uses aria-label on navigation and aria-current="page" on the active page.

When building with DelightStack, keep these accessibility guidelines in mind:

  1. Always provide labels for form inputs. The label prop on Input, Select, Checkbox, Toggle, and Radio generates a properly associated <label>. If you need a visually hidden label, use CSS to hide it while keeping it accessible.

  2. Use descriptive button text. Avoid generic text like “Click here”. If a Button only contains an icon, provide a tooltip or aria-label for screen readers.

  3. Provide alt text for images. The Image, Gallery, and Carousel components accept alt text. Always describe the meaningful content of the image.

  4. Test with keyboard only. Navigate your interface using only Tab, Enter, Space, and arrow keys. Every interactive element should be reachable and operable.

  5. Test with a screen reader. Tools like VoiceOver (macOS), NVDA (Windows), or Orca (Linux) reveal issues that visual testing misses.

  6. Do not disable focus styles. Components have visible focus indicators using --color-focus-ring. If you customize styles, ensure focus remains visible.