Add Accessibility
Document ARIA attributes as JSDoc annotations in component specs and validate WCAG 2.2 Level AA compliance.
See also: The accessibility prompt contains the same workflow as a standalone prompt.
When to use
When the user asks to "add accessibility", "add ARIA", "fix a11y", "document keyboard navigation", "add @aria annotations", or ensure a component is WCAG 2.2 Level AA compliant.
Inputs
| Input | Required | Description |
|---|---|---|
| Component name | Yes | kebab-case, e.g., checkbox, date-picker, grid |
| Scope | No | annotations (add @aria/@keyboard), apply (annotations + JSX attrs), full (audit + validate). Default: full |
Architecture
The spec rendering IS the accessibility specification. ARIA attributes are applied directly in the JSX render function. The @aria JSDoc annotations document what the component renders — they are not a test target.
The test runner (npm run test:a11y) only validates WCAG compliance via axe-core in JSDOM — no browser required. There is no ARIA rule comparison against a spec list.
JSDoc annotation system
All accessibility documentation lives in the spec file as structured JSDoc tags.
Two JSDoc blocks per component
[imports]
[type definitions]
/** ← COMPONENT BLOCK
* @aria {attr} desc ← ARIA attributes this component renders
* @ux {feature} desc ← UX interaction behaviors
*/
export const ComponentName: KendoComponent<...>
[render function]
ComponentName.states = ...
ComponentName.options = ...
ComponentName.folderName = ...
/** ← BOTTOM BLOCK
* @keyboard {trigger} desc ← Keyboard shortcuts
* @see https://url text ← WAI-ARIA specs, WCAG references
*/
export default ComponentName;
Tag reference
@aria — ARIA attributes (component block)
@aria {role="combobox"} Announces the input as a combobox widget.
@aria {aria-haspopup="listbox"} Indicates a listbox popup is available.
@aria {aria-expanded="true"|"false"} Announces popup visibility state.
@aria {aria-label|aria-labelledby} Required accessible name.
@aria {aria-disabled="true"} Rendered only when the component is disabled.
@aria {aria-controls} Points to the listbox id when popup is open.
Rules:
- One tag per distinct attribute
|for alternatives:{aria-label|aria-labelledby}- Include value context:
{aria-expanded="true"|"false"} - State-dependent:
{aria-disabled="true"} Rendered only when disabled - Sub-components each get their own
@ariaannotations
@ux — UX behaviors (component block)
@ux {Fixed position} Stays visible while the user scrolls the page.
@ux {Item selection} Clicking an item transfers focus and selection to it.
@keyboard — Keyboard shortcuts (bottom block)
Uses KeyboardEvent.key names:
@keyboard {Tab} Moves focus to the next focusable element.
@keyboard {Shift + Tab} Moves focus to the previous element.
@keyboard {Enter or Space} Activates the focused item.
@keyboard {Alt + ArrowDown} Opens the popup.
@keyboard {Control/Cmd(Mac) + Home} Moves focus to the first item.
@see — References (bottom block)
@see https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ WAI-ARIA Combobox Pattern
@see https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html WCAG 4.1.2 Name, Role, Value — combobox must have accessible name
WCAG Understanding page URLs:
1.1.1→/non-text-content.html1.3.1→/info-and-relationships.html4.1.2→/name-role-value.html4.1.3→/status-messages.html
Procedure
Step 1: Gather context
packages/html/src/[component]/
├── [component].spec.tsx ← main spec
├── [sub-component].spec.tsx ← sub-component specs (if any)
├── templates/*.tsx ← what actually renders
└── index.ts
Read the spec(s) and templates. Identify:
- What HTML elements and CSS classes are rendered
- Which elements are interactive
- What states exist (disabled, selected, expanded, etc.)
- Whether sub-components have their own spec files
Good reference components to review: autocomplete, button, combobox, tabstrip
Step 2: Add @aria annotations
Add/update the component JSDoc block before export const X: KendoComponent.
What to annotate:
- Role — implicit from
<button>,<nav>,<input>OR explicitrole= - Accessible name —
aria-label,aria-labelledby,title - State —
aria-expanded,aria-disabled,aria-selected,aria-checked,aria-pressed - Range values —
aria-valuenow,aria-valuemin,aria-valuemax - ID references —
aria-controls,aria-describedby,aria-activedescendant - Ownership —
aria-owns
Sub-components (separate .spec.tsx files) get their own @aria block covering only what they render. Example: tabstrip.spec.tsx has @aria {role="tablist"}, while tabstrip-item.spec.tsx has @aria {role="tab"}.
Step 3: Apply ARIA attributes in JSX
Rules:
- Semantic HTML first —
<button>over<div role="button"> - Attributes after
className - Conditional values — use
undefinedto omit:aria-expanded={opened ? 'true' : 'false'} - Icon-only elements — require
aria-label disabledpropagation — pass to all interactive children- Dynamic IDs — use
nextId(prefix)frommisc; use a variable when two elements reference the same ID
// ✅ Correct attribute placement and conditional values
<input
className="k-input-inner"
role="combobox"
aria-expanded={opened ? 'true' : 'false'}
aria-controls={opened ? `${id}-listbox` : undefined}
aria-label={ariaLabel}
aria-disabled={disabled ? 'true' : undefined}
/>
Step 4: Add @keyboard and @see to the bottom block
After all static property assignments, add the bottom JSDoc block:
ComponentName.folderName = FOLDER_NAME;
/**
* @keyboard {ArrowDown} Moves focus to the next item.
* @keyboard {Escape} Closes the popup.
*
* @see https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ WAI-ARIA Combobox Pattern
*/
export default ComponentName;
Step 5: Validate WCAG compliance
npm run build --prefix packages/html
npm run test:a11y [component]
Target: 0 violations.
For each violation:
- Read the axe-core rule description
- Fix the HTML structure or add missing attributes
- Create additional templates for states that need coverage
- Re-run until clean
npm run test:a11y [component] -- --build # Build first then test
Step 6: Regenerate docs
npm run build --prefix packages/html
npm run docs --prefix packages/html
Verify packages/html/docs/[component].md shows correct sections:
- Component Style Options — from
Component.options(auto-generated) - ARIA Attributes — from
@ariaannotations - Keyboard Navigation — from
@keyboardannotations - UX Behavior — from
@uxannotations - Resources — from
@seeannotations
Commands reference
npm run test:a11y # All components
npm run test:a11y button # One component
npm run test:a11y -- --build # Build HTML package first
npm run docs --prefix packages/html # Regenerate all docs