Skip to content

Accessibility

Nim UI is built with accessibility as a core requirement, not an afterthought. All components target WCAG 2.1 Level AA compliance and follow WAI-ARIA authoring practices. This guide covers the accessibility features built into the library and how to maintain accessibility in your applications.

WCAG 2.1 AA Compliance

Nim UI components are designed to meet the four WCAG principles:

Perceivable

  • All text meets minimum contrast ratios (4.5:1 for normal text, 3:1 for large text)
  • Non-text content has text alternatives
  • Content can be presented in different ways without losing meaning
  • Components work without color as the sole indicator of state

Operable

  • All functionality is available from the keyboard
  • Users have enough time to interact with content (configurable timeouts on Toast)
  • No content causes seizures or physical reactions
  • Navigation mechanisms are consistent and predictable

Understandable

  • Text is readable and understandable
  • Components behave in predictable ways
  • Error messages are clear and actionable
  • Labels and instructions are provided for user input

Robust

  • Content is compatible with assistive technologies
  • Valid ARIA attributes are used correctly
  • Components maintain accessibility across browsers and screen readers

ARIA Attributes by Component

Nim UI components include the appropriate ARIA attributes out of the box.

Button

// Standard button - no extra ARIA needed
<Button variant="primary">Save Changes</Button>
// Renders: <button type="button">Save Changes</button>
// Loading button - disables interaction and announces state
<Button loading>Saving...</Button>
// Renders: <button disabled aria-busy="true">Saving...</button>
// Icon-only button - requires aria-label
<Button variant="ghost" aria-label="Close dialog">
<XIcon />
</Button>
<Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
<p>Are you sure you want to proceed?</p>
</Modal>
// Renders with:
// - role="dialog"
// - aria-modal="true"
// - aria-labelledby pointing to the title
// - Focus trap keeping focus within the modal
// - Focus restored to trigger element on close

Alert

<Alert variant="danger" title="Error">
Your session has expired. Please log in again.
</Alert>
// Renders with:
// - role="alert"
// - aria-live="assertive" (for danger/warning)
// - aria-live="polite" (for info/success)

Toast

showToast({
message: 'Changes saved successfully',
variant: 'success',
});
// Renders with:
// - role="status"
// - aria-live="polite"
// - Auto-dismissal respects prefers-reduced-motion

Tabs

<Tabs
tabs={[
{ id: 'profile', label: 'Profile', content: <ProfileTab /> },
{ id: 'settings', label: 'Settings', content: <SettingsTab /> },
]}
/>
// Renders with:
// - role="tablist" on the tab container
// - role="tab" on each tab button
// - role="tabpanel" on each content panel
// - aria-selected on the active tab
// - aria-controls linking tabs to panels
// - Arrow key navigation between tabs

Tooltip

<Tooltip content="Edit this item">
<Button variant="ghost" aria-label="Edit">
<EditIcon />
</Button>
</Tooltip>
// Renders with:
// - role="tooltip" on the tooltip content
// - aria-describedby linking the trigger to the tooltip
// - Shows on focus and hover
// - Dismisses on Escape

Form Inputs

<Input
aria-label="Email address"
aria-invalid={!!errors.email}
aria-describedby="email-error"
/>
{errors.email && (
<span id="email-error" role="alert">
{errors.email}
</span>
)}

Keyboard Navigation

All interactive Nim UI components support keyboard navigation. Below is a reference for the keyboard patterns used.

Global Patterns

KeyAction
TabMove focus to next interactive element
Shift + TabMove focus to previous interactive element
EnterActivate focused button or link
SpaceActivate focused button, toggle checkbox
EscapeClose modal, popover, tooltip, or dropdown

Button

KeyAction
EnterActivate the button
SpaceActivate the button
KeyAction
EscapeClose the modal
TabCycle focus within the modal (focus trap)
Shift + TabCycle focus backwards within the modal

Tabs

KeyAction
Arrow LeftMove to previous tab
Arrow RightMove to next tab
HomeMove to first tab
EndMove to last tab
Enter / SpaceActivate the focused tab

Select / Dropdown

KeyAction
Enter / SpaceOpen dropdown
Arrow DownHighlight next option
Arrow UpHighlight previous option
EnterSelect highlighted option
EscapeClose dropdown

Popover

KeyAction
Enter / SpaceToggle popover (click trigger)
EscapeClose popover
TabMove focus within popover content

Screen Reader Support

Nim UI components are tested with popular screen readers including VoiceOver (macOS), NVDA (Windows), and JAWS (Windows).

Announcements

Components announce meaningful state changes to screen readers:

// Loading states
<Button loading>Submitting...</Button>
// Screen reader: "Submitting..., button, busy"
// Error states
<Input error="Email is required" />
// Screen reader: "Email, invalid entry, Email is required"
// Progress
<Progress value={75} showLabel />
// Screen reader: "Progress, 75 percent"
// Alerts
<Alert variant="success">File uploaded successfully</Alert>
// Screen reader announces: "File uploaded successfully"

Live Regions

Dynamic content updates are announced through ARIA live regions:

// Polite announcements (non-urgent)
<div aria-live="polite">
{searchResults.length} results found
</div>
// Assertive announcements (urgent)
<div role="alert" aria-live="assertive">
{errorMessage}
</div>

Hidden Decorative Elements

Icons and decorative elements that do not convey information are hidden from screen readers:

// Decorative icon - hidden from AT
<span aria-hidden="true">★</span>
// Informative icon - accessible label provided
<span role="img" aria-label="Warning">⚠️</span>

Focus Management

Proper focus management is critical for keyboard and screen reader users.

Focus Visible Styles

All Nim UI components include visible focus indicators for keyboard navigation. These styles only appear when navigating with the keyboard, not when clicking with a mouse.

/* Applied to all interactive components */
:focus-visible {
outline: 2px solid var(--color-primary-500);
outline-offset: 2px;
}

Focus Trapping

Modal and Popover components trap focus within their content area to prevent users from accidentally interacting with background content:

// Focus is automatically trapped when the modal opens
<Modal isOpen={isOpen} onClose={handleClose}>
<Input placeholder="First field (receives focus)" />
<Input placeholder="Second field" />
<Button onClick={handleClose}>Close</Button>
{/* Tab cycles between these elements only */}
</Modal>

Focus Restoration

When a modal or popover closes, focus is returned to the element that triggered it:

function EditDialog() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{/* Focus returns here when the modal closes */}
<Button onClick={() => setIsOpen(true)}>
Edit
</Button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<p>Edit content...</p>
<Button onClick={() => setIsOpen(false)}>Done</Button>
</Modal>
</>
);
}

Color Contrast Guidelines

All Nim UI color combinations meet WCAG AA contrast requirements.

Minimum Contrast Ratios

Content TypeMinimum RatioStandard
Normal text (< 18px)4.5:1WCAG AA
Large text (>= 18px or 14px bold)3:1WCAG AA
Interactive elements (borders, icons)3:1WCAG AA
Decorative elementsNo requirement-

Testing Contrast

Use these tools to verify contrast ratios in your custom themes:

  • Chrome DevTools — Inspect element, view contrast ratio in the color picker
  • WebAIM Contrast Checkerwebaim.org/resources/contrastchecker
  • Stark — Browser extension and Figma plugin for accessibility checking
  • axe DevTools — Automated accessibility testing in the browser

Do Not Rely on Color Alone

Always pair color with another visual indicator:

// Good - icon + color + text
<Alert variant="danger">
<AlertIcon /> {/* Visual icon indicator */}
Error: File upload failed {/* Descriptive text */}
</Alert>
// Good - badge with text label
<Badge variant="success">Active</Badge>
// Bad - color is the only indicator
<span className="text-red-500">●</span> {/* No text alternative */}

Reduced Motion

Nim UI respects the prefers-reduced-motion media query. When enabled, animations and transitions are minimized or removed:

/* Applied internally by Nim UI */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

In your custom components, respect this preference:

import { useEffect, useState } from 'react';
function usePrefersReducedMotion() {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReducedMotion(mediaQuery.matches);
const handler = (e: MediaQueryListEvent) => {
setPrefersReducedMotion(e.matches);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);
return prefersReducedMotion;
}

Accessibility Checklist

Use this checklist when building with Nim UI:

Structure

  • Pages have a logical heading hierarchy (h1 > h2 > h3)
  • Landmark regions are used (main, nav, aside, footer)
  • Page title is descriptive and unique

Interactive Elements

  • All buttons have visible text or aria-label
  • All form inputs have associated labels
  • Error messages are programmatically connected to inputs
  • Custom interactive elements are keyboard accessible

Visual

  • Text contrast meets 4.5:1 (normal) or 3:1 (large)
  • Focus indicators are visible on all interactive elements
  • Content does not rely on color alone to convey meaning
  • Layout works at 200% zoom

Dynamic Content

  • Modals trap focus and restore it on close
  • Toast and alert content is announced via live regions
  • Loading states are communicated to screen readers
  • Route changes are announced in single-page applications

Testing

  • Tested with keyboard only (no mouse)
  • Tested with VoiceOver or NVDA
  • Tested with axe DevTools or Lighthouse
  • Tested with prefers-reduced-motion enabled

Resources

What’s Next?