WCAG 2.1 compliance report — 22 June 2026
Compliance Score
100%
29
Total Checks
29
Passed
0
Warnings
0
Failed
All badges now use icon + color — not color alone. WCAG 1.4.1 compliant.
Status Badges
Priority Badges
Layout.tsx contains <a href="#main-content"> with sr-only + focus:not-sr-only classes. Appears on first Tab press.
<main id="main-content" tabIndex={-1}> accepts programmatic focus from skip link without visible ring.
DataTable sortable <th> elements have tabIndex=0, respond to Enter/Space keydown events, and carry aria-sort attribute.
Radix UI Dialog/Sheet primitives handle focus trapping natively. useFocusTrap hook provided for custom overlays.
Radix Dialog/Sheet/DropdownMenu all dismiss on Escape by default. useKeyboardDismiss hook available for custom overlays.
Mobile hamburger button has aria-label="Open navigation menu" and aria-expanded={mobileOpen} to communicate state.
Header buttons: search (aria-label="Open search"), dark mode toggle (aria-label="Switch to light/dark mode"), notifications, user menu all have descriptive aria-labels.
Status badge icons include aria-hidden="true" since the text label already conveys meaning. No duplicate announcement.
AvatarImage receives alt={user.name}. AvatarFallback provides initials as text alternative when image fails.
Pagination row count <p> has aria-live="polite" aria-atomic="true" so screen readers announce filter result changes.
Notification bell button aria-label includes unread count: "Notifications — 12 unread".
When sidebar is collapsed (icons only), each NavItemRow has aria-label={item.label} so screen readers still announce the nav link name.
Sidebar <nav> has aria-label="Main navigation". Section <ul> elements have aria-label={section.title}.
All Header buttons are h-9 w-9 (36px). Nav items are py-2 with full-width touch area. Mobile FAB is 56px.
First/Previous/Next/Last page buttons all have aria-label attributes. Disabled state communicated via disabled attribute.
DataTable search <Input> has aria-label equal to the searchPlaceholder prop.
Pagination <Select> has aria-label="Rows per page". Adjacent <span> is decorative only.
React-hook-form + shadcn FormMessage renders errors inline below each field. FormField passes aria attributes through FormControl.
Header Breadcrumb has <nav aria-label="Breadcrumb">. Current page breadcrumb item is a <span> (non-link).
All DataTable <th> elements now include scope="col" for proper screen reader association with data cells.
Sortable <th> elements carry aria-sort="ascending"|"descending"|"none" based on TanStack Table sort state.
All Sheet and Dialog components use @radix-ui/react-dialog which sets role="dialog" aria-modal="true" automatically.
DataTable selection checkboxes have aria-label="Select all" / "Select row". Indeterminate state uses checked="indeterminate".
StatusBadge now renders a contextual icon (CheckCircle2, XCircle, AlertTriangle, Clock, etc.) alongside color. showIcon prop defaults to true.
PriorityBadge uses Siren (emergency), AlertOctagon (critical/urgent), AlertTriangle (high), ArrowUp (medium), ArrowDown (low) icons.
Both StatusBadge and PriorityBadge carry role="status" and aria-label with the text value for screen reader announcement.
NotifIcon in Header uses AlertCircle (alert), AlertTriangle (warning), CheckCircle2 (success), Info (system) — icon + color.
globals.css defines :focus-visible with 2px outline using hsl(var(--ring)). Applies to all interactive elements as fallback.
globals.css includes @media (prefers-reduced-motion: reduce) that disables all CSS transitions and animations.
Screen reader announcements across key components
aria-label on icon-only controls
Bell button → "Notifications — 12 unread"aria-sort on sortable table headers
<th scope="col" aria-sort="ascending">aria-live="polite" for dynamic updates
Row count changes → politely announcedaria-expanded for toggleable panels
Sidebar hamburger → aria-expanded={mobileOpen}role="status" on badges
<Badge role="status" aria-label="Active">aria-modal via Radix primitives
Dialog/Sheet → role="dialog" aria-modal="true" autoBreadcrumb navigation landmark
<nav aria-label="Breadcrumb">Skip navigation link
<a href="#main-content" className="sr-only focus:not-sr-only">