Accessibility

Built-in accessibility features following WAI-ARIA standards with keyboard navigation, focus management, and screen reader support.

Overview

All components are designed with accessibility as a first-class concern, not an afterthought.

@svelte-atoms/core follows the WAI-ARIA Authoring Practices Guide (APG) to ensure components are accessible to users of assistive technologies. Every component includes proper ARIA attributes, keyboard navigation, focus management, and semantic HTML structure by default. You don't need to add these features manually—they're built in.

Core Accessibility Features

Keyboard Navigation

Full keyboard support with Tab, Arrow keys, Enter, Space, Escape, Home, and End

ARIA Attributes

Automatic role, state, and property attributes following ARIA specifications

Focus Management

Focus trapping for modals, focus restoration on close, and visible focus indicators

Screen Readers

Proper announcements, semantic structure, and live region support

Semantic HTML

Native elements and proper element types for better accessibility

Color Contrast

WCAG AA compliant color contrast ratios for all theme colors

Keyboard Navigation

All interactive components support standard keyboard interactions out of the box.

Universal Keys

Tab Navigate between focusable elements
Shift+Tab Navigate backwards
Enter Activate buttons and links
Space Activate buttons and toggle checkboxes
Escape Close dialogs, drawers, and popovers

List & Menu Navigation

↑ ↓ Navigate menu items and list items
Home Jump to first item
End Jump to last item

Tabs Navigation

← → Navigate between tabs
Home Jump to first tab
End Jump to last tab

Component Examples

<!-- Button component -->
<Button.Root>
  Click me
</Button.Root>
<!-- Supports: Enter, Space -->

<!-- Menu component -->
<Menu.Root>
  <Menu.Trigger base={Button}>Open Menu</Menu.Trigger>
  <Menu.List>
    <Menu.Item>Option 1</Menu.Item>
    <Menu.Item>Option 2</Menu.Item>
  </Menu.List>
</Menu.Root>
<!-- Supports: Arrow keys, Enter, Escape -->

<!-- Tabs component -->
<Tabs.Root>
  <Tabs.Header>
    <Tabs.Tab value="1">Tab 1</Tabs.Tab>
    <Tabs.Tab value="2">Tab 2</Tabs.Tab>
  </Tabs.Header>
</Tabs.Root>
<!-- Supports: Arrow keys, Home, End -->

ARIA Attributes

Components automatically manage ARIA attributes for roles, states, and relationships.

ARIA (Accessible Rich Internet Applications) attributes provide semantic information to assistive technologies. All components in @svelte-atoms/core automatically apply and manage the appropriate ARIA attributes based on component state. You don't need to manually add these—the Bond system handles them reactively.

Automatically Managed Attributes:

role - Semantic element role
aria-expanded - Collapsed/expanded state
aria-selected - Selection state
aria-disabled - Disabled state
aria-checked - Checkbox state
aria-hidden - Visibility state
aria-labelledby - Label association
aria-describedby - Description association
aria-controls - Element relationships
aria-live - Live region updates

Automatic ARIA Management

<!-- Dialog with ARIA attributes -->
<Dialog.Root bind:open>
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>
        <!-- Automatically sets aria-labelledby -->
        Confirm Action
      </Dialog.Title>
      <Dialog.Description>
        <!-- Automatically sets aria-describedby -->
        Are you sure you want to continue?
      </Dialog.Description>
    </Dialog.Header>
  </Dialog.Content>
</Dialog.Root>

<!-- Accordion with state attributes -->
<Accordion.Root>
  <Accordion.Item>
    <Accordion.Item.Header>
      <!-- aria-expanded automatically managed -->
      Section Title
    </Accordion.Item.Header>
    <Accordion.Item.Body>
      <!-- aria-controls automatically set -->
      Content
    </Accordion.Item.Body>
  </Accordion.Item>
</Accordion.Root>

<!-- Checkbox with mixed state -->
<Checkbox 
  bind:checked 
  bind:indeterminate
>
  <!-- aria-checked="mixed" when indeterminate -->
  Accept terms
</Checkbox>

Focus Management

Automatic focus trapping, restoration, and visible focus indicators for better keyboard navigation.

Focus Features

1.

Focus Trap

Modal components (Dialog, Drawer) trap focus within their boundaries. Tab and Shift+Tab cycle through focusable elements without leaving the modal.

2.

Focus Restoration

When a modal closes, focus automatically returns to the element that triggered it. This prevents users from losing their place in the document.

3.

Initial Focus

Overlays automatically focus the first focusable element when opened, or the overlay itself if no focusable children exist.

4.

Visible Focus Indicators

All interactive elements have clear focus indicators using the :focus-visible pseudo-class.

Focus Trap Example

<script>
  let dialogOpen = $state(false);
</script>

<Dialog.Root bind:open={dialogOpen}>
  <Dialog.Content>
    <!-- Focus automatically trapped within dialog -->
    <!-- Tab cycles through focusable elements -->
    <!-- Shift+Tab reverses direction -->
    
    <Dialog.Header>
      <Dialog.Title>Modal Dialog</Dialog.Title>
    </Dialog.Header>
    
    <Dialog.Body>
      <input type="text" placeholder="First field" />
      <input type="text" placeholder="Second field" />
    </Dialog.Body>
    
    <Dialog.Footer>
      <Button onclick={() => dialogOpen = false}>Cancel</Button>
      <Button>Confirm</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>

Focus Restoration Example

<script>
  import { Popover } from '@svelte-atoms/core/components/popover';
  
  let open = $state(false);
</script>

<Popover.Root bind:open>
  <Popover.Trigger>
    <!-- Focus returns here when popover closes -->
    Open Popover
  </Popover.Trigger>
  
  <Popover.Content>
    <!-- Focus moves to first focusable element when opened -->
    <Button>Action 1</Button>
    <Button>Action 2</Button>
  </Popover.Content>
</Popover.Root>

Semantic HTML

Components use proper semantic HTML elements and can be customized with the `as` prop.

Semantic HTML uses elements that convey meaning about the content they contain. This helps screen readers, search engines, and other tools understand the structure and purpose of your content. All components default to semantic elements, and you can override them using the as prop when needed.

Semantic Structure

<!-- Card with proper semantic structure -->
<Card.Root>
  <Card.Header>
    <!-- Uses semantic header element -->
    <Card.Title>Card Title</Card.Title>
  </Card.Header>
  
  <Card.Body>
    <!-- Main content area -->
    Content goes here
  </Card.Body>
  
  <Card.Footer>
    <!-- Uses semantic footer element -->
    Footer content
  </Card.Footer>
</Card.Root>

<!-- List with proper semantics -->
<List.Root as="ul">
  <!-- Renders as <ul> element -->
  <List.Item>Item 1</List.Item>
  <List.Item>Item 2</List.Item>
</List.Root>

<!-- Accordion with semantic structure -->
<Accordion.Root as="ul">
  <!-- Can render as ul/ol for semantic lists -->
  <Accordion.Item as="li">
    <Accordion.Item.Header>Section</Accordion.Item.Header>
    <Accordion.Item.Body>Content</Accordion.Item.Body>
  </Accordion.Item>
</Accordion.Root>

Screen Reader Support

Proper labeling, announcements, and semantic structure for assistive technologies.

Screen readers rely on proper semantic structure, ARIA attributes, and accessible labels to convey information to users. Components ensure that all interactive elements have accessible names, state changes are announced, and relationships between elements are clear.

Screen Reader Best Practices:

Accessible Names: Use aria-label or aria-labelledby for icon-only buttons
Live Regions: Use role="alert" or aria-live for dynamic content
Descriptions: Associate help text with inputs using aria-describedby
State Changes: State attributes like aria-expanded announce automatically

Screen Reader Patterns

<!-- Alert with proper role -->
<Alert variant="destructive" role="alert">
  <!-- Announced immediately by screen readers -->
  <Alert.Title>Error</Alert.Title>
  <Alert.Description>
    Something went wrong
  </Alert.Description>
</Alert>

<!-- Button with accessible label -->
<Button aria-label="Close dialog">
  <!-- Icon only, needs aria-label -->
  <XIcon />
</Button>

<!-- Form field with proper association -->
<Form.Field>
  <Label for="email">Email Address</Label>
  <Input id="email" type="email" />
  <Form.Description>
    <!-- Associated with input via aria-describedby -->
    We'll never share your email
  </Form.Description>
</Form.Field>

Custom ARIA Attributes

Extend components with custom ARIA attributes using variants or props.

While components provide sensible ARIA defaults, you can add or override attributes using either the variant system (for global configuration) or by passing them directly as props. The variant system is particularly powerful for applying ARIA attributes based on component state.

Variants with ARIA

<script>
  import { defineVariants } from '@svelte-atoms/core/utils';
  
  const alertVariants = defineVariants({
    class: 'rounded-lg p-4 border',
    variants: {
      variant: {
        error: {
          class: 'bg-destructive/10 text-destructive',
          role: 'alert',
          'aria-live': 'assertive'
        },
        warning: {
          class: 'bg-yellow-50 text-yellow-900',
          role: 'status',
          'aria-live': 'polite'
        }
      }
    }
  });
</script>

<!-- Variants automatically apply ARIA attributes -->
<HtmlAtom variants={alertVariants} variant="error">
  <!-- role="alert" and aria-live="assertive" applied -->
  Critical error message
</HtmlAtom>

Disabled States

Proper handling of disabled states with visual cues and ARIA attributes.

Disabled elements are excluded from keyboard navigation and properly announced to screen readers. Components use aria-disabled (for custom elements) or the native disabled attribute (for form inputs) along with visual styling to indicate their state.

Disabled State Handling

<!-- Button with disabled state -->
<Button disabled>
  <!-- aria-disabled automatically set -->
  <!-- Pointer events disabled -->
  <!-- Visual opacity applied -->
  Disabled Button
</Button>

<!-- Menu item with disabled state -->
<Menu.Root>
  <Menu.Trigger base={Button}>Menu</Menu.Trigger>
  <Menu.List>
    <Menu.Item disabled>
      <!-- aria-disabled="true" -->
      <!-- tabindex="-1" to skip in navigation -->
      Disabled Option
    </Menu.Item>
  </Menu.List>
</Menu.Root>

<!-- Form input with disabled state -->
<Input disabled />
<!-- Native disabled attribute -->
<!-- Excluded from form submission -->

Focus Indicators

Clear visual focus indicators for keyboard navigation users.

All interactive components include visible focus indicators using the :focus-visible pseudo-class. This ensures keyboard users can see which element has focus, while avoiding focus rings on mouse clicks. You can customize focus styles using the --ring CSS custom property.

Focus Styles

<!-- All interactive components have focus styles -->
<style>
  /* Applied automatically by components */
  :focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
  }
</style>

<!-- Buttons -->
<Button>
  <!-- Focus ring automatically applied -->
  Click me
</Button>

<!-- Links -->
<Link href="/page">
  <!-- Focus ring with offset -->
  Navigate
</Link>

<!-- Custom focus styles -->
<Button class="focus:ring-4 focus:ring-primary">
  <!-- Override with custom styles -->
  Custom Focus
</Button>

Reduced Motion Support

Respect user preferences for reduced motion animations.

Components that include animations automatically respect the prefers-reduced-motion media query. When users enable reduced motion in their system settings, animations are either disabled or significantly reduced. You should follow this pattern in your custom animations as well.

Reduced Motion Pattern

<script>
  import { Accordion } from '@svelte-atoms/core/components/accordion';
  
  // Components respect prefers-reduced-motion
</script>

<Accordion.Root>
  <Accordion.Item>
    <!-- Animation duration automatically reduced -->
    <!-- when user prefers reduced motion -->
    <Accordion.Item.Header>Section</Accordion.Item.Header>
    <Accordion.Item.Body>
      Content with reduced motion support
    </Accordion.Item.Body>
  </Accordion.Item>
</Accordion.Root>

<!-- Custom animations should check preference -->
<script>
  import { animate } from 'motion';
  
  function handleEnter(element) {
    const prefersReducedMotion = window.matchMedia(
      '(prefers-reduced-motion: reduce)'
    ).matches;
    
    return animate(
      element,
      { opacity: [0, 1] },
      { duration: prefersReducedMotion ? 0 : 0.3 }
    );
  }
</script>

Color Contrast

All theme colors meet WCAG AA standards for color contrast ratios.

The default theme is designed to meet WCAG AA standards with a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text. All foreground/background color combinations have been tested for sufficient contrast. When creating custom themes, ensure your color pairs maintain these ratios.

Accessible Color Pairs

<!-- All theme colors meet WCAG AA standards -->

<!-- Primary actions (4.5:1 minimum) -->
<Button variant="primary">
  <!-- bg-primary / text-primary-foreground -->
  Primary Action
</Button>

<!-- Secondary actions -->
<Button variant="secondary">
  <!-- bg-secondary / text-secondary-foreground -->
  Secondary Action
</Button>

<!-- Destructive actions -->
<Button variant="destructive">
  <!-- bg-destructive / text-destructive-foreground -->
  Delete
</Button>

<!-- Muted text (minimum 4.5:1) -->
<p class="text-muted-foreground">
  Secondary information with proper contrast
</p>

Accessibility Testing

Test accessibility with automated tools and manual keyboard/screen reader testing.

Testing Checklist

1.

Keyboard Navigation

Test that all interactive elements can be reached and activated using only the keyboard (Tab, Enter, Space, Arrow keys).

2.

Screen Reader Testing

Test with NVDA (Windows), JAWS (Windows), or VoiceOver (macOS/iOS) to ensure content is properly announced and navigable.

3.

Automated Testing

Use tools like axe DevTools, Lighthouse, or WAVE to catch common accessibility issues automatically.

4.

Color Contrast

Verify all text meets WCAG contrast requirements using contrast checker tools or browser extensions.

5.

Focus Indicators

Ensure focus indicators are clearly visible when navigating with keyboard.

Testing Example TypeScript
<!-- Add data-testid for testing -->
<Button data-testid="submit-button">
  Submit
</Button>

<!-- Use presets to apply test IDs globally -->
<script>
  import { setPreset } from '@svelte-atoms/core/context';
  
  setPreset({
    button: () => ({
      'data-component': 'button',
      'data-testid': 'button-element'
    }),
    dialog: () => ({
      'data-component': 'dialog',
      'data-testid': 'dialog-element'
    })
  });
</script>

<!-- Testing with Playwright -->
<script lang="ts">
  // In your test file
  import { test, expect } from '@playwright/test';
  
  test('button is keyboard accessible', async ({ page }) => {
    await page.goto('/');
    
    // Tab to button
    await page.keyboard.press('Tab');
    
    // Verify focus
    await expect(page.getByTestId('submit-button')).toBeFocused();
    
    // Activate with Enter
    await page.keyboard.press('Enter');
  });
</script>

Best Practices

Guidelines for building accessible applications with @svelte-atoms/core.

1.

Use Semantic HTML

Prefer semantic elements (<button>, <nav>, <main>) over generic divs with ARIA roles.

2.

Provide Accessible Labels

All interactive elements need accessible names. Use visible labels or aria-label for icon-only buttons.

3.

Don't Override ARIA

Components manage ARIA attributes automatically. Only override them if you have a specific need and understand the implications.

4.

Test with Real Users

Automated tools catch common issues, but testing with real assistive technology users is invaluable for finding usability problems.

5.

Maintain Focus Order

Ensure tab order follows visual flow. Avoid using positive tabindex values as they disrupt natural order.

6.

Ensure Sufficient Contrast

Always verify color contrast meets WCAG AA standards (4.5:1 for normal text, 3:1 for large text).

7.

Support Keyboard Navigation

Every interactive element reachable by mouse must also be reachable and operable by keyboard alone.

8.

Respect User Preferences

Honor system settings like reduced motion, high contrast mode, and dark mode preferences.