Inclusive Component Recipes
Why this chapter matters
Accessibility at scale is won or lost in reusable components. If menus, dialogs, tabs, tables, notifications, cards, tooltips, and theme controls are accessible by default, product teams inherit good behavior. If they are not, every page becomes a separate remediation project.
Inclusive Components provides the source pattern for this chapter: accessibility is not a decorative audit layer. It is interaction design, semantic HTML, keyboard behavior, focus management, naming, state, and announcement discipline encoded in components.
Core mental model
An inclusive component recipe is a reusable contract for semantics, state, focus, keyboard, announcement, layout, and failure behavior.
Each shared component should answer:
- What native element or role represents this behavior?
- What is the keyboard model?
- Where does focus start, move, and return?
- What is the accessible name?
- Which states must be exposed?
- Which changes need announcements?
- How does it behave with zoom, reduced motion, touch, and screen readers?
- What must product teams not override?
Source-derived architecture notes
- Native semantics should be the default. ARIA is a repair tool for cases where native elements cannot express the interaction.
- Menu buttons, tabs, dialogs, collapsibles, sliders, and data tables need state diagrams before styling decisions.
- Tooltips and toggletips solve different problems: one supplements a control, the other exposes information users may need to intentionally open and read.
- Notifications need live-region strategy, priority, dismissal behavior, persistence rules, and avoidance of noisy repeated announcements.
- Cards are often misused as giant links. The interaction model must preserve meaningful headings, link purpose, focus order, and nested actions.
- Theme switchers are accessibility features when they respect contrast, user preference, motion preference, and persistence.
Architecture decision framework
| Component | Contract default | Escape hatch | Tradeoff signal |
|---|---|---|---|
| Dialog | Focus trap, labelled title, return focus | Non-modal panel | Use non-modal when background work must remain accessible. |
| Menu button | Button trigger and managed item navigation | Simple disclosure | Use disclosure when choices are links or static content. |
| Tabs | Roving focus and panel association | Segmented links | Use links when navigation changes route. |
| Data table | Semantic table with captions/headers | Grid widget | Use grid only for spreadsheet-like editing. |
| Notification | Polite/assertive live-region policy | Silent visual status | Announce when status affects task completion. |
Implementation patterns
Baseline pattern for small teams
Create a component recipe template:
- purpose and non-purpose
- semantic structure
- keyboard model
- focus rules
- naming rules
- state matrix
- reduced-motion and zoom behavior
- automated checks
- manual keyboard and screen reader checklist
Use it for the highest-risk primitives first: dialog, menu/disclosure, tabs, form field, notification, and data table.
Scale pattern for multi-team organizations
Publish recipes as design-system contracts:
- Storybook examples for every state
- design annotations for names, roles, focus, and announcement behavior
- unit tests for state and keyboard logic
- accessibility tests for roles and names
- visual regression for zoom and density modes
- release notes for behavior changes
- prohibited override list for focus, role, and keyboard behavior
The contract should let product teams style and compose without weakening behavior.
Migration-safe pattern for legacy systems
Audit repeated patterns before fixing pages:
- Identify all menu, dialog, tab, table, card, and notification implementations.
- Pick one canonical recipe per pattern.
- Ship a compatibility wrapper when immediate replacement is risky.
- Replace components by user journey, not by folder.
- Delete old variants once adoption and regression checks are in place.
Anti-patterns and failure modes
- Symptom: keyboard users get trapped or lost after closing a dialog. Root cause: focus return was not part of the component contract. Prevention control: focus lifecycle tests.
- Symptom: screen reader users hear repeated noisy alerts. Root cause: notifications use live regions without priority or deduplication rules. Prevention control: live-region policy and announcement tests.
- Symptom: a data-heavy table becomes unusable with assistive tech. Root cause: visual layout replaced table semantics. Prevention control: semantic table contract or deliberate grid pattern.
- Symptom: cards have unclear link purpose. Root cause: entire card clickability hides meaningful actions. Prevention control: explicit heading/link/action structure.
- Symptom: product teams break accessibility while styling. Root cause: component exposes role, focus, or keyboard internals as casual props. Prevention control: stable behavior API with protected internals.
Verification checklist
- Every shared primitive documents purpose, semantics, keyboard behavior, focus, names, states, and announcements.
- Dialogs trap focus only when modal and return focus on close.
- Menus, tabs, and collapsibles have deterministic keyboard models.
- Data tables preserve captions, headers, sorting semantics, and responsive behavior.
- Notifications define live-region priority, persistence, and dismissal.
- Cards preserve meaningful headings, links, and nested actions.
- Theme controls respect contrast, persisted preference, and OS/user settings.
Metrics and scorecards
Track leading indicators:
- number of shared primitives with complete recipes
- duplicate component variants by pattern
- keyboard test coverage for primitives
- manual assistive technology defects by component
- adoption rate of canonical recipes
Track lagging indicators:
- accessibility regressions caused by component changes
- blocked critical flows for keyboard or screen reader users
- design-system migration delay caused by missing states
Deep component recipes
Dialog recipe
| Contract area | Requirement |
|---|---|
| Semantics | Dialog has an accessible name from a visible title or explicit label. |
| Focus entry | Opening moves focus to the dialog or first meaningful control. |
| Focus containment | Modal dialogs keep focus inside while open. Non-modal panels do not pretend to be modal. |
| Focus return | Closing returns focus to the trigger or a logical successor if the trigger disappeared. |
| Escape behavior | Escape closes dismissible dialogs; destructive or blocking flows need explicit policy. |
| Background | Inert or hidden background content is not reachable to keyboard or assistive tech. |
| Error state | Validation errors inside the dialog are announced and focused predictably. |
Architectural decision: decide whether the component is a modal dialog, non-modal panel, popover, or route. Many inaccessible dialogs are actually route-level tasks squeezed into overlays.
Menu button and disclosure recipe
Menus are for commands or choices with menu-like keyboard behavior. Disclosures are for showing and hiding content. Navigation lists are often just lists of links. Do not use a menu pattern because it looks compact.
| Pattern | Use when | Avoid when |
|---|---|---|
| Menu button | actions are command-like and need arrow-key navigation | links are normal page navigation |
| Disclosure | a button reveals related content | choices require command semantics |
| Select/listbox | user chooses one value from a controlled set | choices are links or rich actions |
Contract requirements:
- trigger is a button
- expanded/collapsed state is exposed
- keyboard behavior is documented
- focus movement is deterministic
- outside click and Escape behavior match product expectation
- disabled items are handled consistently
Tabs recipe
Tabs are not just styled buttons. They bind a tablist, tabs, and tab panels.
Decide:
- automatic activation or manual activation
- whether panels are preloaded, lazy, or persistent
- how focus moves with arrow keys
- whether route changes should use links instead of tabs
- whether hidden panels remain in DOM and why
Failure mode: tab UI that changes route, resets state, and behaves like navigation while exposing itself as a widget. If the browser back button matters, links or route-aware tabs may be the better architecture.
Data table recipe
Data tables need semantic structure before responsive styling:
- caption or surrounding heading explains purpose
- column and row headers are explicit
- sort state is exposed
- pagination and filtering are keyboard reachable
- empty, loading, and error states preserve table context
- responsive collapse does not destroy relationships between header and cell
- virtualized tables keep assistive technology behavior in mind
Do not promote a table into an ARIA grid unless users need spreadsheet-like cell navigation or editing. A grid has a much heavier keyboard contract.
Notification recipe
Notifications need priority and persistence policy:
| Type | Announcement | Persistence |
|---|---|---|
| Success | polite or no announcement if already obvious | short-lived unless needed for confirmation |
| Warning | polite, visible until understood | dismissible or tied to next action |
| Error | assertive only when urgent | persistent until resolved or dismissed |
| Background sync | polite, deduplicated | visible status if user action depends on it |
Avoid dumping every toast into an assertive live region. That creates noise and can interrupt task flow.
Card recipe
Cards often combine heading, image, summary, primary link, and secondary actions. The architecture question is whether the card is:
- an article preview
- a product tile
- a selectable object
- a command surface
- a container for separate actions
Each shape has different semantics. Avoid nested interactive elements inside a giant clickable card. Prefer a clear heading link plus explicit secondary actions. The focus order should match visual importance.
Tooltip and toggletip recipe
Tooltips are supplemental labels or hints. They should not contain essential interactive content. Toggletips are user-invoked disclosures for information that may need to be read, copied, or explored.
| Pattern | Trigger | Content | Keyboard expectation |
|---|---|---|---|
| Tooltip | hover/focus | short nonessential text | appears on focus, dismisses predictably |
| Toggletip | button | useful explanatory content | opens/closes by button, focus remains understandable |
| Popover/dialog | button | interactive content | explicit focus and dismissal contract |
Theme switcher recipe
A theme switcher should account for:
- system preference
- stored user preference
- contrast requirements
- reduced motion
- forced colors or high-contrast modes
- SSR/first paint mismatch
- accessible label that names the next or current state clearly
Theme is not only brand. It can be an accessibility setting.
Design-system enforcement model
For every recipe, enforce behavior at three layers:
| Layer | Enforcement |
|---|---|
| Design | annotated Figma/design specs for names, states, and focus |
| Code | protected primitive internals, tests, lint rules, and examples |
| Release | accessibility checks, manual keyboard review, migration notes |
The strongest design systems do not ask every product engineer to become an accessibility specialist. They make correct behavior hard to accidentally remove.
Exercises
- Write a recipe for one existing dialog: labels, focus entry, focus trap, close behavior, and return focus.
- Compare a menu button and disclosure pattern in your product. Decide which behavior is actually needed.
- Audit one card grid for link purpose, heading structure, nested actions, and focus order.
- Define a notification policy for success, warning, error, and background sync states.
Source Lens
This chapter is synthesized from:
- Inclusive Components - toggle buttons, menus, tooltips, theme switchers, tabs, collapsibles, sliders, notifications, data tables, modal dialogs, and cards.
- Advanced Front-End Development - component thinking, styling, accessibility, forms, testing, and design-system practice.
- WCAG-oriented material already present in this guide - component-level contracts and critical-flow verification.