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
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 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.