Accessibility

Last updated on 2026-03-26

The Social Media Dashboard Kit is built with WCAG AA accessibility as a baseline. Every component and page meets or exceeds these standards, using semantic HTML, ARIA attributes, and accessible interaction patterns throughout all 35+ screens.

Every page includes a skip-to-content link as the first focusable element:

import { SkipLink } from "@/components/layout/skip-link"

<SkipLink />

The skip link is visually hidden until focused. It jumps the user directly to the #main-content landmark, bypassing the sidebar and header navigation.

Color Contrast

All text meets WCAG AA contrast ratios:

  • Normal text (< 18px): minimum 4.5:1 contrast ratio
  • Large text (>= 18px bold or >= 24px): minimum 3:1 contrast ratio
  • UI components and states: minimum 3:1 against adjacent colors

The oklch color token system ensures contrast is maintained in both light and dark mode. The teal/cyan theme (hue 175) was specifically tuned so that primary-on-background and foreground-on-muted combinations pass AA thresholds.

Platform Colors and Contrast

The six platform brand colors (Instagram pink, Twitter blue, Facebook blue, LinkedIn blue, TikTok teal, YouTube red) are used in badges, chart segments, and calendar pills. Each platform color is paired with a contrasting foreground color (white or dark text) to meet AA requirements. On dark backgrounds, platform colors are slightly lightened to maintain contrast.

Keyboard Navigation

All interactive elements are keyboard accessible:

  • Tab -- navigate between focusable elements in logical order
  • Shift+Tab -- navigate backwards
  • Enter/Space -- activate buttons, links, and toggles
  • Arrow keys -- navigate within menus, radio groups, tabs, calendar cells, and conversation lists
  • Escape -- close dialogs, sheets, popovers, and the command palette

Focus Indicators

Visible focus rings appear on all interactive elements using the --ring design token:

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

Focus Traps

Focus is trapped inside overlay components to prevent keyboard users from tabbing behind them:

  • Dialog -- post delete confirmation, report generation dialog, media detail panel
  • Sheet -- mobile sidebar navigation, day detail sheet on the content calendar
  • Command -- global search command palette (Cmd+K)

When these components close, focus returns to the trigger element that opened them.

Screen Reader Support

Semantic HTML

All pages use proper HTML5 landmarks:

  • <header> for the app header
  • <nav> with aria-label for sidebar navigation, mobile navigation, settings sub-navigation, and analytics tab navigation
  • <main> for primary content area
  • <aside> for the sidebar and inbox thread panel
  • <section> with aria-label for dashboard widgets, analytics sections, and report sections
  • <time> with datetime attribute for post timestamps, schedule dates, and conversation timestamps

ARIA Labels

Icon-only buttons include descriptive labels:

<Button aria-label="Search posts">
  <Search className="size-4" />
</Button>

<Button aria-label="Switch to dark mode">
  <Moon className="size-5" />
</Button>

<Button aria-label="View Instagram dashboard">
  <PlatformIcon platform="instagram" className="size-5" />
</Button>

Live Regions

The LiveRegion component announces dynamic content changes to screen readers:

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

<LiveRegion message="Post rescheduled to March 15" politeness="polite" />

Live regions are used for post schedule changes, calendar drag-and-drop operations, conversation status updates, report generation completion, and media upload confirmations.

Visually Hidden

The VisuallyHidden component hides content visually while keeping it accessible to screen readers:

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

<VisuallyHidden>Instagram engagement rate: 4.2%</VisuallyHidden>

Heading Hierarchy

Every page follows a strict heading hierarchy:

  • One <h1> per page (the page title)
  • <h2> for major sections
  • <h3> for subsections within those sections
  • No skipped heading levels

For example, the analytics overview uses <h1> for "Analytics", <h2> for "Engagement Trends", "Platform Comparison", and "Content Performance" sections, and <h3> within each section as needed.

Touch Targets

All interactive elements meet the 44x44px minimum touch target size. The kit includes a .touch-target utility class:

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

This class is applied to icon buttons in the app header (search, notifications, user menu), sidebar navigation items, calendar post pills on mobile, conversation list items, and platform toggle buttons.

Reduced Motion

The kit respects the prefers-reduced-motion media query. When enabled:

@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 all animations including calendar drag-and-drop transitions, chart entrance animations, sidebar collapse/expand transitions, notification slide-ins, shimmer loading states, hover transforms on post cards and platform cards, and the top posts carousel autoplay.

Status Indicators

Status information is never conveyed by color alone. The kit uses color plus icon plus text:

{/* Post status uses icon + text + color */}
<CheckCircle className="size-3 text-chart-5" />
<span className="text-chart-5">Published</span>

{/* Sentiment uses icon + text + color */}
<TrendingUp className="size-3 text-chart-5" />
<span className="text-chart-5">Positive sentiment</span>

{/* Engagement trend uses icon + text + color */}
<ArrowUp className="size-3 text-chart-5" />
<span className="text-chart-5">+12.4% vs last period</span>

The PostStatusBadge and PlatformBadge components combine color-coded badges with readable text labels (Published, Scheduled, Draft, Instagram, Twitter, etc.) so no information depends solely on color.

Form Accessibility

Forms across the kit follow accessibility best practices:

  • Labels associated with inputs via htmlFor/id
  • Error messages linked with aria-describedby
  • Required fields marked with visual indicators and aria-required
  • Invalid fields marked with aria-invalid for screen reader announcement
  • The post composer, settings forms, team invite forms, and connected accounts forms all follow these patterns
  • Zod validation errors are announced to screen readers when forms are submitted

Calendar Keyboard Accessibility

The content calendar built with @hello-pangea/dnd supports full keyboard interaction:

  • Space -- lift and drop a post pill
  • Arrow keys -- move a lifted pill between day cells (left/right for adjacent days, up/down for same weekday in previous/next week)
  • Escape -- cancel a drag operation and return the pill to its original date

Each calendar day cell includes an aria-label describing the date and post count. Screen readers announce when a post pill is picked up, moved to a new date, or dropped into position.

Inbox Keyboard Navigation

The social inbox conversation list supports keyboard navigation:

  • Arrow Up/Down -- navigate between conversations in the list
  • Enter -- open the selected conversation in the thread panel
  • Tab -- move focus from the conversation list to the thread panel and reply input
  • Escape -- close the thread panel and return focus to the conversation list

Each conversation item includes an aria-label with the username, platform, and unread count.

Testing Recommendations

Tool Purpose
axe DevTools Automated accessibility scanning
VoiceOver (macOS) Screen reader testing
NVDA (Windows) Screen reader testing
Keyboard-only navigation Tab through every page
Lighthouse Accessibility audit score