Accessibility Components

Last updated on 2026-04-14

The A11y Starter Kit includes three utility components that handle common accessibility requirements. These are lightweight, standalone components you can drop into any React project.

LiveRegion

An ARIA live region that screen readers monitor for dynamic content changes.

File: components/a11y/live-region.tsx

import LiveRegion from '@/components/a11y/live-region';

// In your root layout
export default function RootLayout({ children }) {
    return (
        <html>
            <body>
                {children}
                <LiveRegion />
            </body>
        </html>
    );
}

How it works:

  • Renders a visually hidden <div aria-live="polite"> at the bottom of the page
  • The useAnnounce hook writes messages into this region
  • Screen readers detect the content change and announce it
  • Messages are cleared after a short delay to prevent re-announcement

When to use:

  • Toast/snackbar notifications
  • Form submission confirmations
  • Status updates (e.g., "3 items selected")
  • Loading state changes

VisuallyHidden

Renders text that is invisible to sighted users but announced by screen readers.

File: components/a11y/visually-hidden.tsx

import VisuallyHidden from '@/components/a11y/visually-hidden';

// Add context for screen readers
<button>
    <TrashIcon aria-hidden="true" />
    <VisuallyHidden>Delete item</VisuallyHidden>
</button>

// Add descriptive text to a badge
<span className="badge">3</span>
<VisuallyHidden>unread notifications</VisuallyHidden>

CSS technique:

The component uses the standard visually-hidden CSS pattern:

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

This keeps the text in the DOM (so screen readers find it) while hiding it visually.

When to use:

  • Icon-only buttons that need text labels
  • Providing context that is visually obvious but not programmatically apparent
  • Adding "for screen readers" instructions
  • Skip link text

A link that appears on focus and lets keyboard users skip repetitive navigation.

File: components/layout/ (part of the layout)

// Already included in the root layout
<a href="#main-content" className="skip-link">
    Skip to main content
</a>

// Target element
<main id="main-content">
    {/* Page content */}
</main>

Behavior:

  • Hidden by default (off-screen)
  • Appears when focused via Tab key
  • Clicking it moves focus to #main-content
  • Styled with high contrast for visibility

WCAG requirement: Skip links satisfy WCAG 2.4.1 (Bypass Blocks), a Level A criterion.

Using These Components Together

A typical accessible page uses all three:

export default function Page() {
    const announce = useAnnounce();

    const handleAction = () => {
        performAction();
        announce('Action completed');
    };

    return (
        <>
            {/* SkipLink is in the root layout */}
            <main id="main-content">
                <button onClick={handleAction}>
                    <CheckIcon aria-hidden="true" />
                    <VisuallyHidden>Mark as complete</VisuallyHidden>
                </button>
            </main>
            {/* LiveRegion is in the root layout */}
        </>
    );
}