Accessibility
Reshaped is built with accessibility in mind, following WAI-ARIA best practices and ensuring all components work seamlessly with assistive technologies. Every component includes proper ARIA attributes, keyboard navigation, and focus management.
ARIA Attributes
Reshaped components automatically include appropriate ARIA attributes based on their role and state.
Modal Dialogs
Modals are properly marked up as dialogs with label associations:
import { Modal } from 'reshaped';
function AccessibleModal() {
return (
<Modal
active={isOpen}
onClose={handleClose}
ariaLabel="User Settings"
>
<Modal.Title>Settings</Modal.Title>
<Modal.Subtitle>Manage your account preferences</Modal.Subtitle>
{/* Modal content */}
</Modal>
);
}
Generated markup:
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-123-title"
aria-describedby="modal-123-subtitle"
>
<h2 id="modal-123-title">Settings</h2>
<p id="modal-123-subtitle">Manage your account preferences</p>
</div>
Button Groups
Button groups are marked as grouped controls:
import { Button } from 'reshaped';
function ButtonGroup() {
return (
<Button.Group>
<Button>Left</Button>
<Button>Center</Button>
<Button>Right</Button>
</Button.Group>
);
}
Generates:
<div role="group">
<button>Left</button>
<button>Center</button>
<button>Right</button>
</div>
Loading States
Buttons with loading states include proper ARIA labels:
import { Button } from 'reshaped';
<Button
loading
loadingAriaLabel="Saving changes"
>
Save
</Button>
Icon-Only Buttons
Always provide accessible labels for icon-only buttons:
import { Button, Icon } from 'reshaped';
import { IconZap } from '@iconify/react';
<Button
icon={IconZap}
attributes={{ 'aria-label': 'Quick action' }}
/>
Icon-only buttons without labels will be inaccessible to screen reader users. Always include an aria-label.
Keyboard Navigation
Reshaped components support full keyboard navigation following standard patterns.
Focus Management
All interactive components are keyboard-accessible and include visible focus indicators:
import { Button } from 'reshaped';
// Focus indicators are automatic
<Button onClick={handleClick}>Click me</Button>
Focus styles are automatically applied:
[data-rs-keyboard] button:focus {
outline: 2px solid var(--rs-color-foreground-primary);
outline-offset: 2px;
}
Keyboard Mode Detection
Reshaped detects keyboard usage and applies appropriate focus styles only during keyboard navigation:
import { useKeyboardMode } from '@reshaped/headless';
function Component() {
const isKeyboard = useKeyboardMode();
return (
<div>
{isKeyboard ? 'Using keyboard' : 'Using mouse/touch'}
</div>
);
}
The data-rs-keyboard attribute is automatically added to the document when keyboard navigation is detected.
Arrow Key Navigation
Components like menus and lists support arrow key navigation:
import { DropdownMenu, MenuItem } from 'reshaped';
function Menu() {
return (
<DropdownMenu>
{/* Use arrow keys to navigate */}
<MenuItem>Option 1</MenuItem>
<MenuItem>Option 2</MenuItem>
<MenuItem>Option 3</MenuItem>
</DropdownMenu>
);
}
Supported keys:
- Arrow Up/Down - Navigate through items
- Home/End - Jump to first/last item
- Enter/Space - Activate item
- Escape - Close menu
Custom Keyboard Shortcuts
Use the useHotkeys hook for custom keyboard shortcuts:
import { useHotkeys } from '@reshaped/headless';
function Editor() {
useHotkeys([
['mod+s', () => saveDocument()],
['mod+k', () => openCommandPalette()],
]);
return <div>Editor content</div>;
}
The mod key maps to Cmd on macOS and Ctrl on Windows/Linux.
Screen Reader Support
Reshaped components work seamlessly with screen readers like NVDA, JAWS, and VoiceOver.
Hidden Visually Content
Hide content visually while keeping it accessible to screen readers:
import { HiddenVisually } from 'reshaped';
function IconButton() {
return (
<button>
<Icon svg={IconTrash} />
<HiddenVisually>Delete item</HiddenVisually>
</button>
);
}
This renders content that is:
- Hidden from sighted users
- Announced by screen readers
- Not removed from the DOM
Live Regions
For dynamic content updates, use ARIA live regions:
import { Toast } from 'reshaped';
function Notifications() {
return (
<Toast
active={showToast}
attributes={{ 'aria-live': 'polite' }}
>
Changes saved successfully
</Toast>
);
}
Live region politeness levels:
- polite - Announced at next opportunity (default for toasts)
- assertive - Announced immediately (use sparingly)
- off - Not announced
Form Labels
Always associate labels with form controls:
import { TextField, FormControl } from 'reshaped';
function LoginForm() {
return (
<FormControl>
<FormControl.Label>Email address</FormControl.Label>
<TextField
name="email"
type="email"
inputAttributes={{
'aria-describedby': 'email-hint',
'aria-invalid': hasError,
}}
/>
<FormControl.Hint id="email-hint">
We'll never share your email
</FormControl.Hint>
{hasError && (
<FormControl.Error>
Please enter a valid email address
</FormControl.Error>
)}
</FormControl>
);
}
Focus Management
Focus Rings
Focus indicators are automatically styled and can be customized:
import { Actionable } from 'reshaped';
// Standard focus ring
<Actionable insetFocus={false}>
Standard focus
</Actionable>
// Inset focus ring (fits inside the element)
<Actionable insetFocus>
Inset focus
</Actionable>
// Disable focus ring (use sparingly)
<Actionable disableFocusRing>
No focus ring
</Actionable>
Only disable focus rings when you provide an alternative focus indicator. Never remove focus indicators entirely.
Focus Trapping
Modals and overlays automatically trap focus:
import { Modal } from 'reshaped';
function Dialog() {
return (
<Modal active={isOpen} onClose={handleClose}>
{/* Focus is trapped within the modal */}
<input type="text" />
<Button onClick={handleClose}>Close</Button>
</Modal>
);
}
Focus behavior:
- Focus moves to modal when opened
- Tab cycles through modal elements only
- Escape key closes modal
- Focus returns to trigger element on close
Scroll Locking
Overlays prevent background scrolling:
import { useScrollLock } from '@reshaped/headless';
function CustomOverlay() {
const [isOpen, setIsOpen] = useState(false);
// Lock scrolling when overlay is open
useScrollLock(isOpen);
return (
<div>
{/* Overlay content */}
</div>
);
}
Color Contrast
Reshaped themes meet WCAG 2.1 Level AA contrast requirements:
- Normal text: Minimum 4.5:1 contrast ratio
- Large text: Minimum 3:1 contrast ratio
- UI components: Minimum 3:1 contrast ratio
Checking Contrast
All color tokens are designed to meet contrast requirements:
import { View, Text } from 'reshaped';
// Meets contrast requirements
<View backgroundColor="primary">
<Text color="on-background-primary">
High contrast text
</Text>
</View>
Color pairings are automatically calculated to ensure accessibility across light and dark modes.
Touch Targets
Interactive elements meet minimum touch target sizes (44x44px):
import { Button, Actionable } from 'reshaped';
// Buttons automatically meet size requirements
<Button size="small">Still accessible</Button>
// Add touch hitbox for small custom elements
<Actionable touchHitbox>
<Icon svg={IconX} size={4} />
</Actionable>
Reduced Motion
Respect user’s motion preferences:
@media (prefers-reduced-motion: reduce) {
.animated {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Reshaped components automatically respect prefers-reduced-motion.
Testing Accessibility
Automated Testing
Reshaped includes accessibility tests for all components:
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { Button } from 'reshaped';
expect.extend(toHaveNoViolations);
test('Button is accessible', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Manual Testing
Test your application with:
- Keyboard only - Navigate without a mouse
- Screen reader - Use NVDA, JAWS, or VoiceOver
- Color contrast - Check contrast in both color modes
- Zoom - Test at 200% zoom level
- Voice control - Test with voice navigation
Best Practices
- Always provide labels - Every interactive element needs a label
- Maintain focus order - Keep tab order logical and intuitive
- Use semantic HTML - Leverage native element semantics
- Test with real users - Include people with disabilities in testing
- Support keyboard shortcuts - Provide keyboard alternatives to mouse actions
- Don’t rely on color alone - Use icons, text, and patterns
- Provide clear feedback - Confirm actions with appropriate announcements
- Maintain contrast - Ensure text remains readable
Resources
Related