Accessibility

Last updated on 2026-04-05

The Finance Dashboard Kit is built with WCAG 2.1 AA accessibility from the ground up. Every screen -- from the transaction table to the expense 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 TransactionActions() {
  const announce = useAnnounce()

  function handleDelete(description: string) {
    // ... deletion logic
    announce(`Transaction "${description}" has been deleted`)
  }
}

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 AddTransactionDialog({ 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 AccountGrid({ accounts }: Props) {
  const { activeIndex, handleKeyDown } = useKeyboardNavigation(accounts.length)
  return <div onKeyDown={handleKeyDown}>...</div>
}

use-reduced-motion

Detects the user's prefers-reduced-motion media query setting. Chart animations, card entrance animations, and transitions are disabled when this preference is active.

import { useReducedMotion } from "@/hooks/use-reduced-motion"

function IncomeExpenseChart() {
  const prefersReducedMotion = useReducedMotion()
  return <BarChart isAnimationActive={!prefersReducedMotion} ... />
}

use-mobile

Detects mobile viewport breakpoints for responsive UI adjustments. Used by the sidebar to render a sheet on mobile and a fixed sidebar on desktop.

import { useMobile } from "@/hooks/use-mobile"

function AppSidebar() {
  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 to reach the main content area.

import { SkipLink } from "@/components/layout/skip-link"
// Rendered in the root layout, targets #main-content

The skip link uses position: absolute off-screen and transitions to top: 1rem on focus with a visible primary-colored button style.

Live Region

An ARIA live region component for announcing dynamic content changes (filter results, form submissions, budget alerts).

import { LiveRegion } from "@/components/a11y/live-region"

Visually Hidden

Provides text content for screen readers without visual display. Used for icon-only buttons and decorative elements that need accessible labels.

import { VisuallyHidden } from "@/components/a11y/visually-hidden"

<button>
  <TrashIcon />
  <VisuallyHidden>Delete transaction</VisuallyHidden>
</button>

Screen Reader Headings

Every page includes a screen-reader-only <h1> heading using the sr-only class to ensure proper document structure, even when the visual heading uses <h2>:

<h1 className="sr-only">Dashboard</h1>
<h2 className="font-heading text-2xl font-semibold">Good morning</h2>

Keyboard Navigation

All interactive elements are keyboard accessible:

Element Keys
Sidebar navigation Tab, Arrow Up/Down, Enter
Transaction table Tab through rows, Enter to view detail
Filter controls Tab between inputs, Enter/Space to toggle
Dialog forms Tab cycles within, Escape to close
Sheet panels Tab cycles within, Escape to close
Tabs (Bills, Reports, Settings) Arrow Left/Right to switch tabs
Budget progress cards Tab through cards, Enter for details
Goal cards Tab through grid, Enter to open detail sheet
Command palette Cmd+K to open, Arrow Up/Down to navigate, Enter to select
Theme switcher Tab through options, Enter/Space to select
Widget picker Tab through toggles, Space to toggle visibility

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 across all 6 color themes. The oklch color system ensures perceptually uniform contrast across the palette.

Financial amounts use semantic coloring (green for income/positive, red for expenses/negative) with sufficient contrast in both modes.

Focus Management

All interactive elements display a visible focus ring using the --ring token:

:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

Focus is properly managed when:

  • Opening dialogs (focus moves to first focusable element)
  • Closing dialogs (focus returns to trigger)
  • Opening sheets (focus trapped within)
  • Navigating between tabs (focus follows active tab panel)

Touch Targets

All interactive elements have a minimum touch target size of 44x44px, meeting WCAG 2.1 success criterion 2.5.5. The .touch-target CSS utility class is applied to navigation items, buttons, and interactive cards:

.touch-target {
  min-width: 44px;
  min-height: 44px;
}

Reduced Motion

The kit respects the prefers-reduced-motion media query at the CSS level:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

This disables:

  • Card entrance animations (fade-in-up, scale-in)
  • Chart animations (Recharts isAnimationActive)
  • Floating and shimmer effects
  • Smooth scrolling

Testing Recommendations

  • Screen reader: Test with VoiceOver (macOS) or NVDA (Windows)
  • Keyboard: Navigate the entire app without a mouse -- verify all tables, forms, dialogs, and navigation are reachable
  • Color contrast: Use the browser DevTools accessibility panel across all 6 themes in both light and dark mode
  • Reduced motion: Enable "Reduce motion" in system preferences and verify chart animations are disabled and card entrances are instant
  • Zoom: Test at 200% zoom to ensure layouts remain usable and no content is clipped

Need a professional accessibility review? We run audits for Next.js and React apps starting at $499.