Docs

Accessibility

Vamos UI targets WCAG 2.2 level AA. This page shows how that is tested, what each component does on the keyboard, and what is still open.

Target

Every component aims for WCAG 2.2 AA in both themes and both materials: the WAI-ARIA Authoring Practices pattern for its role, full keyboard operation, a visible focus ring (focus-ring), names for icon-only controls, 4.5:1 text contrast, 24 px minimum targets for pointer controls, logical properties for right-to-left layouts and no motion when the user asks for less. Interactive primitives are built on Radix, which handles focus management and ARIA wiring for overlays, menus and tabs.

Not a certification
Automated checks find roughly a third of real-world issues. The numbers below are what the test suite enforces today; they are not an audit by a third party.

How it is tested

Component tests (Vitest + axe)

Each component has a test file next to it, run with Vitest, Testing Library and vitest-axe in jsdom. The shared helper expectNoA11yViolations() renders the component inside the providers and fails on any axe violation. Interaction tests drive the real keyboard through user-event.

Counted in packages/uiFilesCalls
Test files205
axe assertions (expectNoA11yViolations / toHaveNoViolations)173318
Keyboard interactions (user.keyboard / user.tab)82284
Right-to-left renders (dir="rtl")57

Counted from the repository when this page was built. jsdom has no layout, so colour contrast is not part of the unit run; it is checked in the browser runs below.

Blocks, docs and example sites (Playwright + axe)

  • 9 Playwright spec files run in Chrome; 8 of them call axe (@axe-core/playwright) and fail on any serious or critical violation.
  • Every admin block and every page of every example site is checked at 1440 px and 390 px, together with one landmark, exactly one visible h1 on site pages, no console errors and no sideways scroll.
  • Docs component pages are smoke-tested across the theme matrix: light and dark, glass and solid.
  • Example sites run axe with colour contrast on. Component and block pages disable the contrast rule, because preview stages put components on decorative backgrounds; contrast for tokens is checked by hand (see known issues).
  • Signature interactions (menus, dialogs, sortable lists, language switch) are driven with the keyboard in the browser.

Reduced motion

Motion in the kit (@vamos-ui/react/motion, overlay and collapse transitions) renders its final state under prefers-reduced-motion: reduce or data-motion="off". 2 spec files run whole suites with Playwright's reducedMotion: "reduce" and assert that every heading and section is visible immediately, at 1440 and 390 px. A separate test keeps layout shift on the animated home pages under the 0.1 CLS budget.

Keyboard support

Every interactive component is operable with the keyboard alone. The table lists the components whose docs page documents keys beyond the platform defaults (Tab, Enter, Space on buttons and fields), taken from each page's notes. Buttons, inputs, checkboxes and switches follow native behaviour.

ComponentGroupKeys
Agenda / schedulerCalendar & timeEvents are focusable in time order. Alt+↑/↓ moves by the snap step, Alt+Shift+↑/↓ resizes, and Alt+←/→ changes the day. The context menu (right-click, Shift+F10) has “Change time…”, a dialog with date and time fields. This is the non-drag path for WCAG 2.5.7.
GanttCalendar & timeThe treegrid is the accessible source of truth, and the timeline is aria-hidden. Use ↑/↓ for rows, ←/→ for cells (on the first cell, ← and → collapse and expand), Home and End, and Enter or F2 to edit a cell. Enter saves and Escape cancels. Date edits are the non-drag path (WCAG 2.5.7).
Resource calendarCalendar & timeThe grid has one tab stop. Use the arrow keys to move, Shift+arrows to select, Enter for the cell menu (assign template, remove, your actions) and Delete to clear (with Undo). Alt+arrows move the cell's blocks. In codes mode, type a code.
Time trackerCalendar & timeThe timesheet is a grid with one tab stop. Use the arrow keys to move, type a digit or press Enter to edit, Enter to save (then move down), Escape to cancel and Delete to clear.
Donut / Pie chartChartsThe ring itself is not focusable; the legend list is the keyboard path.
HeatmapChartsArrows move (calendar: up/down = day, left/right = week), Home / End jump within a row, PageUp / PageDown move a month (calendar) or to the column ends (matrix).
Comment threadData displayThe list is an ARIA feed — PageDown / PageUp move between comments. Reply moves focus into the reply box and back after sending.
Filter barData displayThe bar is one toolbar tab stop; ←/→, Home/End move between chip segments. In the popover type to search fields, Backspace on an empty input goes back a step, Enter applies, Esc cancels.
Pin mapData displayTab enters on the selected (or first) pin, arrows go to the nearest pin in that direction, Home/End jump, Enter/Space toggles selection, Escape clears.
Task listData displayDrag reorder: wrap the list in the Sortable group when you need pointer dragging; onReorder already provides the keyboard alternative.
Tree viewData display↑ ↓ move, → opens or enters a branch, ← closes or goes to the parent (swapped in RTL), Home / End, * opens all siblings, type-ahead (folded: “ist” finds “İstanbul”), Space toggles the check or selection, Enter runs onAction.
Hierarchy chartEditors & drag-and-dropAccessibility follows the Tree view pattern on the nodes: role="tree" with levels, positions and expanded state. ↑ / ↓ go through nodes in reading order, → opens or enters, ← closes or goes to the parent, Home / End, * opens siblings, Enter or Space selects, type-ahead by first letter. The focused node is panned into view.
Image cropperEditors & drag-and-dropFocus the crop area and use the arrow keys to move; the zoom slider takes arrows, Page Up/Down, Home/End.
KanbanEditors & drag-and-dropOn a focused card, Space or Enter lifts it (arrow keys move across columns, Space drops, Esc cancels). Alt + ← / → moves to the adjacent stage (mirrored in RTL), Alt + ↑ / ↓ within the column. The card menu has Move up / down and Move to ▸ every stage. Every move is announced and focus stays on the card.
Permission matrixEditors & drag-and-dropArrows move (roving tabindex, mirrored in RTL), Home/End go to the row edges, Ctrl+Home/End to the grid corners. Space toggles, Enter opens the scope menu, 1–4 pick a scope, Delete resets an override. The header row and row headers are part of the grid: their buttons grant or revoke a whole column or row.
Rich text / block editorEditors & drag-and-dropEsc selects the current block. In block selection, ↑/↓ move between blocks, Alt+Shift+↑/↓ moves the block, ⌘/Ctrl+Shift+D duplicates, Alt+Shift+Z deletes, Enter goes back to the text. Insertions, moves and deletes are announced.
SortableEditors & drag-and-dropFocus a handle, Space or Enter lifts, arrow keys move, Space or Enter drops, Escape cancels. Instructions are attached to every handle via aria-describedby.
Sortable list / treeEditors & drag-and-dropTrees use the Tree view pattern: role="tree", one tab stop, ↑ / ↓ move between rows, → opens or enters, ← closes or goes to the parent, Home / End, * opens siblings, type-ahead by first letter.
ExportFlows & wizardsScope and format are radio groups, columns a checkbox list with select-all and drag or keyboard reordering. File names carry the local date: contacts-2026-11-10.csv. Writing the audit entry is the app's job — onExport receives the reason.
Color pickerForms & inputsThe area and strips are role=slider with dictionary names (“Saturation and brightness”, “Hue”, “Opacity”) and work with arrow keys.
ComboboxForms & inputsWAI-ARIA combobox + listbox: the search box owns aria-activedescendant; ↑/↓ move, Enter selects, Esc closes and returns focus to the trigger.
Line itemsForms & inputsEnter on the last field of the last row adds a row; Alt+↑/↓ moves a row; Ctrl/⌘+Enter inserts below; Ctrl/⌘+Shift+Backspace deletes. The row menu offers the same actions without shortcuts or dragging.
SliderForms & inputsArrows ± step, Page Up / Page Down ± 10 steps, Home / End to the bounds. aria-valuetext carries the formatted value ("$120", "₺1.250").
Tag inputForms & inputsBackspace on the empty input moves focus to the last chip; a second Backspace (or Delete) removes it. ←/→ move between chips and back to the input; typing on a chip returns to the input.
Aspect RatioMediaNothing is focusable on the box itself; keyboard and screen readers reach the child directly.
CarouselMediaWhen focus is inside: ←/→ (↑/↓ vertical), Home, End. RTL flips the arrows and the direction.
Image fieldMediaThe focal marker is a focusable 2-D control: aria-roledescription “focal point”, aria-valuetext “horizontal 62%, vertical 40%”; arrows step 1%, Shift + arrows 10%, Home centres.
LightboxMedia←/→ browse (mirrored in RTL), Home/End, + / − / 0 zoom, Esc close.
Media libraryMedia(grid): arrows move, Home/End (row), Ctrl/⌘ + Home/End (all), Page Up/Down, Space selects, Shift extends, Ctrl/⌘ + A selects all, Enter opens details, Delete asks to delete, Esc clears the selection. The grid uses a roving tabindex, also when virtualised.
Video playerMedia(focus inside the player): Space / K play, ← → 5 s, ↑ ↓ volume, M mute, C captions, F full screen.
AccordionNavigationEnter / Space toggles, ↑ ↓ move between headers, Home / End jump to the first / last.
MenubarNavigationTab reaches the bar once; ← → move between triggers (and keep a menu open while moving); ↓, Enter or Space open; Esc closes and returns focus; typing jumps to an item.
Navigation MenuNavigationTab between items, Enter / Space / ↓ open a panel, Tab moves into it, Escape closes and returns focus to the trigger.
SidebarNavigationTab moves between items (this is site navigation, not role=menu). → (← in RTL) opens a flyout and focuses its first link; ↑↓ move inside; Esc or ← closes and returns focus.
Context MenuOverlays & feedbackInside the menu works like Dropdown Menu: arrows, Home/End, typeahead, → for submenus, Escape to close.
Dropdown MenuOverlays & feedbackEnter, Space or ↓ opens; ↑ ↓ Home End move; typing jumps to an item; → opens a submenu (← in RTL); Escape closes and returns focus to the trigger.
Hover CardOverlays & feedbackHover cards don't open from the keyboard or on touch. The same details must be reachable on the link target (the contact page, the author page).
PopoverOverlays & feedbackEscape closes the popover and returns focus to the trigger.
TooltipOverlays & feedbackThe trigger gets aria-describedby pointing at the tooltip. Focus opens it, Escape closes it.
ResizablePage layouts(WAI-ARIA Window Splitter): focus the handle, then ← → (or ↑ ↓), Home / End; Enter collapses a collapsible pane.
Scroll AreaPage layoutsThe viewport is keyboard-focusable only when its content has nothing focusable of its own.
Date PickerPickersThe field stays typeable; Alt+↓ opens the calendar, where arrows / PageUp / PageDown / Home / End move, Enter picks and Esc closes and returns focus to the field.
Date range pickerPickersPresets are a listbox (↑ ↓ Home End, Enter). The grid uses the WAI-ARIA grid keyboard model. The selected range is announced: “From November 1, 2026 to November 10, 2026, 10 days”.

Known issues

  • Status colours as plain text. The light-theme success colour was darkened from #0f8a5f (4.36:1 on white) to #0b7d55 (5.15:1), and badges and alerts mix the status hue with the text colour, so their 11–13 px labels pass 4.5:1. The raw hues are still below 4.5:1 as small text directly on the light page background (--scene): success 4.48:1, danger 4.39:1, warning 3.41:1. Use them for icons, dots and large text, or use tone-soft / Badge / Alert for small text.
  • Hydration id mismatch under load. A useId mismatch was seen twice on the development server under memory pressure (maintenance and list blocks). It has not reproduced in the static build that ui.vamos.ist serves. While it happens, an aria-labelledby can point at a stale id until React re-renders.
  • Unlabelled tables in two blocks (a detail block and an app shell) log a console warning; they need a caption or aria-label.
  • Floating contact dock can overlap the booking bar on the restaurant and spa example sites at phone width.
  • Charts are images to assistive technology. Each chart frame has a table view and a text summary; the plot itself is not navigable.
  • Hover cards do not open from the keyboard or on touch, by design. Their content must be reachable on the link target.
  • No manual screen-reader pass (VoiceOver, NVDA, JAWS) has been recorded for every component yet. That is the next step before 1.0.

Your part

  • Give every icon-only button a label or aria-label, and every field a visible label through Field.
  • Keep one h1 per page and a <main> landmark; the app shell and site shell provide the others.
  • A custom accent is contrast-checked by npx vamos-ui brand check; run npx vamos-ui verify in CI for the lint rules.
  • Right-click and drag can never be the only path: pair context menus with an action menu and drag with keyboard moves (the kit's sortable parts do this).

Reporting a problem

Found a barrier? Open an issue on GitHub with the component, theme axes, browser and assistive technology. Accessibility bugs are treated like functional bugs.