Accessibility
Last updated on 2026-03-30
The HR Dashboard Kit is built with WCAG 2.1 AA accessibility from the ground up. Every screen -- from the employee directory to the attendance heatmap -- supports keyboard navigation, screen readers, and reduced motion preferences.
Accessibility Hooks
Five custom hooks in the hooks/ directory provide reusable accessibility patterns:
use-announce
Announces dynamic content changes to screen readers using an ARIA live region.
import { useAnnounce } from "@/hooks/use-announce"
function LeaveApproval() {
const announce = useAnnounce()
function handleApprove(name: string) {
// ... approval logic
announce(`Leave request for ${name} has been approved`)
}
}
use-focus-trap
Traps keyboard focus within a container (modals, dialogs, sheets). Focus cycles through focusable elements and returns to the trigger element on close.
import { useFocusTrap } from "@/hooks/use-focus-trap"
function EmployeeModal({ isOpen }: { isOpen: boolean }) {
const ref = useFocusTrap(isOpen)
return <div ref={ref}>...</div>
}
use-keyboard-navigation
Provides arrow key navigation for lists, grids, and custom widgets.
import { useKeyboardNavigation } from "@/hooks/use-keyboard-navigation"
function EmployeeGrid({ employees }: Props) {
const { activeIndex, handleKeyDown } = useKeyboardNavigation(employees.length)
return <div onKeyDown={handleKeyDown}>...</div>
}
use-reduced-motion
Detects the user's prefers-reduced-motion media query setting. Chart animations and calendar transitions are disabled when this preference is active.
import { useReducedMotion } from "@/hooks/use-reduced-motion"
function HeadcountChart() {
const prefersReducedMotion = useReducedMotion()
return <AreaChart isAnimationActive={!prefersReducedMotion} ... />
}
use-mobile
Detects mobile viewport breakpoints for responsive UI adjustments.
import { useMobile } from "@/hooks/use-mobile"
function Sidebar() {
const isMobile = useMobile()
// Render sheet on mobile, fixed sidebar on desktop
}
Accessibility Components
Skip Link
Every page includes a skip navigation link that becomes visible on first Tab press, allowing keyboard users to bypass the sidebar and header.
import { SkipLink } from "@/components/a11y/skip-link"
// Rendered in the root layout, targets #main-content
Live Region
An ARIA live region component for announcing dynamic content changes.
import { LiveRegion } from "@/components/a11y/live-region"
Visually Hidden
Provides text content for screen readers without visual display.
import { VisuallyHidden } from "@/components/a11y/visually-hidden"
<button>
<TrashIcon />
<VisuallyHidden>Delete employee record</VisuallyHidden>
</button>
Keyboard Navigation
All interactive elements are keyboard accessible:
| Element | Keys |
|---|---|
| Sidebar navigation | Tab, Arrow Up/Down, Enter |
| Employee directory | Tab through cards, Enter to view profile |
| Org chart | Arrow keys to navigate tree, Enter to expand/collapse |
| Leave request table | Tab through rows, Enter/Space for actions |
| Attendance heatmap | Arrow keys to navigate days |
| Star rating input | Arrow Left/Right to adjust, Enter to confirm |
| Kanban board | Tab between columns and cards |
| Dialog/Sheet | Tab cycles within, Escape to close |
Color Contrast
All text and interactive elements meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text) in both light and dark modes. The oklch color system ensures perceptually uniform contrast across the palette.
Touch Targets
All interactive elements have a minimum touch target size of 44x44px, meeting WCAG 2.1 success criterion 2.5.5.
Testing Recommendations
- Screen reader: Test with VoiceOver (macOS) or NVDA (Windows)
- Keyboard: Navigate the entire app without a mouse
- Color contrast: Use the browser DevTools accessibility panel
- Reduced motion: Enable "Reduce motion" in system preferences and verify chart animations are disabled