Accessibility Hooks
Last updated on 2026-04-14
The A11y Starter Kit includes 5 custom React hooks that handle common accessibility patterns. All hooks are standalone and can be copied into any React project.
useFocusTrap
Traps keyboard focus within a container element (modals, dialogs, drawers).
File: hooks/use-focus-trap.ts
import { useFocusTrap } from '@/hooks/use-focus-trap';
function Modal({ isOpen, onClose }) {
const ref = useFocusTrap(isOpen);
if (!isOpen) return null;
return (
<div ref={ref} role="dialog" aria-modal="true">
<h2>Modal Title</h2>
<p>Modal content here.</p>
<button onClick={onClose}>Close</button>
</div>
);
}
Behavior:
- On open: focuses the first focusable element inside the container
- Tab/Shift+Tab wraps within the container boundaries
- Escape key closes (if
onCloseis wired up) - On close: returns focus to the element that triggered the modal
useKeyboardNavigation
Enables arrow key navigation for lists, menus, and grids.
File: hooks/use-keyboard-navigation.ts
import { useKeyboardNavigation } from '@/hooks/use-keyboard-navigation';
function Menu({ items }) {
const { activeIndex, containerRef } = useKeyboardNavigation({
itemCount: items.length,
orientation: 'vertical', // or 'horizontal'
});
return (
<ul ref={containerRef} role="menu">
{items.map((item, i) => (
<li key={item.id} role="menuitem" tabIndex={i === activeIndex ? 0 : -1}>
{item.label}
</li>
))}
</ul>
);
}
Behavior:
- Arrow Up/Down (vertical) or Left/Right (horizontal) move between items
- Home/End jump to first/last item
- Only the active item is in the tab order (
tabIndex={0}) - Wraps from last to first and vice versa
useAnnounce
Provides a function to announce messages to screen readers via ARIA live regions.
File: hooks/use-announce.ts
import { useAnnounce } from '@/hooks/use-announce';
function SaveButton() {
const announce = useAnnounce();
const handleSave = async () => {
await saveData();
announce('Changes saved successfully');
};
return <button onClick={handleSave}>Save</button>;
}
Parameters:
message(string) -- the text to announcepoliteness('polite'|'assertive') -- defaults to'polite'
Use 'assertive' for errors and urgent messages. Use 'polite' for confirmations and status updates.
Requires: The <LiveRegion /> component must be rendered in the layout (already included in the root layout).
useReducedMotion
Detects the user's prefers-reduced-motion setting.
File: hooks/use-reduced-motion.ts
import { useReducedMotion } from '@/hooks/use-reduced-motion';
function AnimatedCard() {
const prefersReduced = useReducedMotion();
return (
<div className={prefersReduced ? '' : 'animate-fade-in'}>
Card content
</div>
);
}
Returns: true if the user prefers reduced motion, false otherwise.
Use this to:
- Disable CSS animations and transitions
- Skip auto-playing carousels
- Reduce parallax and scroll effects
useMobile
Detects whether the viewport is below a mobile breakpoint.
File: hooks/use-mobile.ts
import { useMobile } from '@/hooks/use-mobile';
function ResponsiveNav() {
const isMobile = useMobile();
return isMobile ? <MobileMenu /> : <DesktopNav />;
}
Returns: true if the viewport width is below the mobile breakpoint (768px by default).
Using Hooks in Your Own Project
All hooks are standalone files with no external dependencies beyond React. To use them in your project:
- Copy the hook file from
hooks/into your project - Update the import path
- For
useAnnounce, also copycomponents/a11y/live-region.tsxand render it in your root layout