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
List & Menu Navigation
Tabs Navigation
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 rolearia-expanded - Collapsed/expanded statearia-selected - Selection statearia-disabled - Disabled statearia-checked - Checkbox statearia-hidden - Visibility statearia-labelledby - Label associationaria-describedby - Description associationaria-controls - Element relationshipsaria-live - Live region updatesAutomatic 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
Focus Trap
Modal components (Dialog, Drawer) trap focus within their boundaries. Tab and Shift+Tab cycle through focusable elements without leaving the modal.
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.
Initial Focus
Overlays automatically focus the first focusable element when opened, or the overlay itself if no focusable children exist.
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:
aria-label or aria-labelledby for icon-only buttonsrole="alert" or aria-live for dynamic contentaria-describedbyaria-expanded announce automaticallyScreen 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
Keyboard Navigation
Test that all interactive elements can be reached and activated using only the keyboard (Tab, Enter, Space, Arrow keys).
Screen Reader Testing
Test with NVDA (Windows), JAWS (Windows), or VoiceOver (macOS/iOS) to ensure content is properly announced and navigable.
Automated Testing
Use tools like axe DevTools, Lighthouse, or WAVE to catch common accessibility issues automatically.
Color Contrast
Verify all text meets WCAG contrast requirements using contrast checker tools or browser extensions.
Focus Indicators
Ensure focus indicators are clearly visible when navigating with keyboard.
<!-- 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.
Use Semantic HTML
Prefer semantic elements (<button>, <nav>, <main>) over generic divs with ARIA roles.
Provide Accessible Labels
All interactive elements need accessible names. Use visible labels or aria-label for icon-only buttons.
Don't Override ARIA
Components manage ARIA attributes automatically. Only override them if you have a specific need and understand the implications.
Test with Real Users
Automated tools catch common issues, but testing with real assistive technology users is invaluable for finding usability problems.
Maintain Focus Order
Ensure tab order follows visual flow. Avoid using positive tabindex values as they disrupt natural order.
Ensure Sufficient Contrast
Always verify color contrast meets WCAG AA standards (4.5:1 for normal text, 3:1 for large text).
Support Keyboard Navigation
Every interactive element reachable by mouse must also be reachable and operable by keyboard alone.
Respect User Preferences
Honor system settings like reduced motion, high contrast mode, and dark mode preferences.