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

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