From process-and-robustness-principles
Use this skill when the question is whether users can *operate* a UI — keyboard, switch device, voice, screen-reader gestures — not just by mouse or touch. Trigger when designing keyboard navigation, focus management for modals/menus/popovers, drag-and-drop with alternatives, motion-heavy interactions, or any flow that asks the user to do something. Covers WCAG Principle 2 (Operable). Sub-aspect of `accessibility`; read that first if you haven't already.
How this skill is triggered — by the user, by Claude, or both
Slash command
/process-and-robustness-principles:accessibility-operableThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
WCAG Principle 2: user-interface components and navigation must be operable.
WCAG Principle 2: user-interface components and navigation must be operable.
The five sub-criteria, simplified:
The default Tab order follows DOM source order and only includes natively focusable elements: <a href>, <button>, <input>, <select>, <textarea>, <summary>, <iframe>, plus elements with tabindex="0".
<button>, not <div onclick>.tabindex="0" to add custom interactive elements to tab order.tabindex="-1" to make an element programmatically focusable (e.g., for focus management) but not reachable via Tab.tabindex > 0. Positive tabindex creates a custom tab order that's almost always worse than DOM order and bewilders screen readers.The user must see where focus is. Browsers provide default focus rings; never outline: none without a replacement.
/* Right: replace, don't remove */
:focus { outline: none; }
:focus-visible {
outline: 2px solid hsl(220 90% 50%);
outline-offset: 2px;
border-radius: 0.25rem;
}
/* Wrong: removes the only signal a keyboard user has */
*:focus { outline: none; }
:focus-visible (rather than :focus) shows the ring only when focus arrived via keyboard (or non-pointer means), not on every mouse click. This addresses the historical "designers don't want focus rings on click" concern without sacrificing keyboard accessibility.
Honor user expectations:
| Key | Behavior |
|---|---|
Tab | Focus next; Shift+Tab for previous |
Enter / Space | Activate button, follow link, toggle checkbox |
Esc | Close overlay (Dialog, Sheet, Popover, Combobox) |
| Arrow keys | Move within radio groups, sliders, tabs, menus, comboboxes |
Home / End | First / last in a list or menu |
Page Up / Down | Scroll a region |
/ | Often: focus search (de facto convention) |
Cmd/Ctrl+K | Often: open command palette (de facto convention) |
ARIA Authoring Practices Guide documents canonical keyboard interactions for every common pattern.
A keyboard trap is a region where focus enters and cannot leave via keyboard alone. Common offenders:
Esc to close.Test: keyboard-only walkthrough. If you ever get stuck and have to mouse to escape, you have a trap.
When a modal opens:
// Pseudocode — in practice use a library that handles edge cases
function openModal(modal, trigger) {
modal.dataset.previousFocus = trigger;
const focusables = modal.querySelectorAll('a, button, input, [tabindex="0"]');
focusables[0]?.focus();
// ... trap focus on Tab/Shift+Tab within `focusables` ...
}
function closeModal(modal) {
modal.previousFocus?.focus();
}
Most accessible UI libraries (Radix UI, React Aria, headless component sets) do this automatically. Custom-built modals that skip focus management are inaccessible.
<a href="#main" class="skip-link">Skip to main content</a>
<header><nav>... 50 nav items ...</nav></header>
<main id="main">...</main>
<style>
.skip-link {
position: absolute;
left: -10000px;
top: auto;
}
.skip-link:focus {
position: fixed;
top: 8px; left: 8px;
padding: 8px 16px;
background: black; color: white;
z-index: 100;
}
</style>
Keyboard users tab past nav to reach content quickly. Visible only on focus so it doesn't clutter sighted-mouse-user views.
If the system has timeouts (session expiry, captcha re-verify, time-limited form), users must be able to:
Exceptions: real-time events (auctions), where time limit is essential.
<!-- Right: warn before timeout, allow extension -->
<dialog open>
<h2>Session about to expire</h2>
<p>You have 60 seconds before your session ends.</p>
<button onclick="extendSession()">Stay signed in</button>
</dialog>
Content that auto-moves, blinks, or scrolls for more than 5 seconds must be pause-able / stop-able.
This includes carousels, animated banners, news tickers, autoplaying video.
WCAG 2.3.1: don't have content that flashes more than 3 times per second. Photosensitive epilepsy can be triggered by flashing in the 3–55 Hz range.
In practice, this rules out: rapid strobe effects, blinking text (don't), high-contrast flashing animations.
WCAG 2.3.3: respect prefers-reduced-motion. Many users (vestibular disorders, migraine, motion sensitivity) have configured their OS to request reduced motion.
@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;
}
}
For specific motion that's meaningful (a state change indicator), allow it but make it briefer / less dramatic in reduced-motion mode.
Every page has a unique, descriptive <title>:
<title>Settings — Notifications — Acme</title>
Screen reader users hear the title on page load; tab labels show it; browser history relies on it. Generic "Acme" titles for every page are useless.
When a user tabs through a page, focus should follow the visual / logical reading order. CSS-rearranged elements (Grid, Flex order) can break this — test.
Links should be understandable from their text alone (or from text + immediately-adjacent context). Screen reader users navigate by listing all links.
<!-- Wrong: "click here" tells the screen reader user nothing -->
<p>To read more, <a href="/blog/post">click here</a>.</p>
<!-- Right: link text describes destination -->
<p>Read more in <a href="/blog/post">our annual review</a>.</p>
WCAG 2.4.5 (AA): provide more than one way to find a page (search, sitemap, navigation, related-content links). Helps users with cognitive impairments and users who navigate non-linearly.
<h2>Article</h2> — useless. <h2>Q4 sales recap</h2> — useful.
Users need to know where they are. Breadcrumbs, current-page highlighting in nav, page titles all serve this.
<nav>
<a href="/dashboard">Dashboard</a>
<a href="/projects" aria-current="page">Projects</a>
<a href="/team">Team</a>
</nav>
aria-current="page" tells assistive tech which item is current; visual styling reinforces it.
fitts-law-touch-targets)WCAG 2.5.5 (AAA): targets ≥ 44×44 CSS px. WCAG 2.5.8 (AA, 2.2): targets ≥ 24×24 CSS px with adequate spacing.
Multi-pointer or path-based gestures (pinch, swipe, drag) must have single-pointer alternatives:
The user can cancel a pointer action by moving away before lifting. Don't trigger destructive actions on mousedown; trigger on mouseup or click.
The accessible name must include any visible label text. Pattern:
<!-- Right: visible text matches accessible name -->
<button>Save</button>
<!-- Wrong: aria-label overrides visible text, voice control breaks -->
<button aria-label="Submit">Save</button>
<!-- Voice user says "click Save" — fails because the accessible name is "Submit" -->
Functions triggered by device motion (shake, tilt) must have UI alternatives. Some users physically can't shake a device.
<div class="dropdown">
<button id="menu-button"
aria-haspopup="true"
aria-expanded="false"
aria-controls="actions-menu">
Actions
</button>
<ul id="actions-menu"
role="menu"
hidden
aria-labelledby="menu-button">
<li role="menuitem" tabindex="-1">Edit</li>
<li role="menuitem" tabindex="-1">Duplicate</li>
<li role="menuitem" tabindex="-1">Archive</li>
<li role="separator"></li>
<li role="menuitem" tabindex="-1" class="destructive">Delete</li>
</ul>
</div>
JavaScript to handle:
Enter/Space/ArrowDown on the button: open menu, focus first item.ArrowUp/ArrowDown in menu: move focus.Home/End: first / last item.Esc: close, return focus to button.Tab: close, move to next focusable.Plus aria-expanded toggles between "true" and "false".
This is verbose because menus genuinely have a lot of expected behavior. Use Radix, React Aria, or another library that implements WAI-ARIA Menu pattern correctly — don't roll your own.
outline: none without a replacement. Single largest accessibility crime in modern web design.<div onclick>. Not focusable, not keyboard-operable, not announced as a button. Use <button>.mouseout with no keyboard equivalent.Esc, don't trap focus, don't restore focus on close.Esc everywhere. Open every overlay; Esc should close it.prefers-reduced-motion: reduce. macOS: System Settings → Accessibility → Display → Reduce Motion. Are critical animations still meaningful? Are decorative ones gone?accessibility (parent).accessibility-perceivable — what users see; complementary.accessibility-understandable — what users comprehend.accessibility-robust — markup that assistive tech can rely on.fitts-law and fitts-law-touch-targets (interaction) — target size.feedback-loop (interaction) — keyboard interactions need visible feedback too.Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub hdeibler/universal-design-principles --plugin process-and-robustness-principles