test(v2): add axe accessibility gate + keyboard nav tests to Storybook#3691
Merged
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Wire axe-core into the vitest Storybook runner so every v2 lib story is scanned for accessibility violations, gated per story by the @storybook/addon-a11y `a11y.test` parameter (error / todo / off) with a global default of "error". Fix the unlabeled-control violations this surfaces (icon-only buttons, decorative images) and mark the remaining component-level gaps as `todo` so CI stays green while the backlog stays visible. Add keyboard-operability play() tests to RSwitch, RTabNav, and RRating. Refs #1848 Generated-By: PostHog Code Task-Id: b44aba2c-90c9-407d-8c80-5bbe77ecd6c1
gantoine
force-pushed
the
posthog-code/v2-a11y-audit
branch
from
July 6, 2026 02:10
16fc7f1 to
1eb4d1c
Compare
Resolve 7 of the 8 ratcheted `todo` stories with real fixes and flip them back to the `error` gate: - RProgressCircular: add optional `label`; render aria-hidden when the ring is decorative (indeterminate + no label/slot), else name it via label or slot content. Cascades to RSpinner, RSelect (Loading), RTextField (Loading). - RTextField: label the interactive prepend/append-inner adornment button from a new `*InnerLabel` prop (falls back to the tooltip). - RDateField: move role="combobox" off the RTextField `<label>` wrapper onto the field container, restoring the input's label and using an allowed role. - RTable: mark the layout-only scroll/body containers presentational so rows/cells resolve their required table parent in all states. - RMenuItem, RDropzone, RSlider: supply the menu parent / input label / slider aria-label the components already support. RSelect (Clearable) stays `todo`: the clear button is nested inside the activator button and the correct fix is a visual-layout refactor that needs in-browser verification. Refs #1848 Generated-By: PostHog Code Task-Id: b44aba2c-90c9-407d-8c80-5bbe77ecd6c1
Remove redundant explanation and shorten the remaining comments added by the accessibility work to the "why" only. Generated-By: PostHog Code Task-Id: b44aba2c-90c9-407d-8c80-5bbe77ecd6c1
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds an automated accessibility audit layer to the v2 Storybook/Vitest test suite (via axe-core), and fixes/ratchets accessibility + keyboard-operability issues across v2 library components/stories (notably unlabeled controls referenced in #1848).
Changes:
- Add an
axe-coregate to the Vitest Storybook runner with per-storya11y.testcontrols (error/todo/off). - Add Storybook
play()keyboard navigation tests for key primitives (switch/tab nav/rating). - Fix a11y violations across v2 components and stories (labels for icon-only controls, improved roles/ARIA semantics, and component-level accessibility props).
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| frontend/test/storybook.test.ts | Adds Storybook-driven rendering + per-story axe-core scanning with ratcheting modes. |
| frontend/.storybook/preview.ts | Sets the global default a11y.test mode to error to enforce the gate. |
| frontend/package.json | Adds axe-core devDependency. |
| frontend/package-lock.json | Locks axe-core version update/addition. |
| frontend/src/v2/lib/structural/RTooltip/RTooltip.stories.ts | Labels icon-only tooltip activators to satisfy accessible-name requirements. |
| frontend/src/v2/lib/structural/RToolbar/RToolbar.stories.ts | Labels icon-only toolbar buttons in stories. |
| frontend/src/v2/lib/primitives/RTabNav/RTabNav.vue | Makes tab item images decorative (alt="") to avoid redundant/incorrect accessible names. |
| frontend/src/v2/lib/primitives/RTabNav/RTabNav.stories.ts | Adds play() keyboard navigation coverage for tab selection. |
| frontend/src/v2/lib/primitives/RProgressCircular/RProgressCircular.vue | Adds optional label and decorative behavior for unnamed indeterminate spinners. |
| frontend/src/v2/lib/primitives/RProgressCircular/RProgressCircular.stories.ts | Exercises the new label control and labels determinate examples. |
| frontend/src/v2/lib/overlays/RCarousel/RCarousel.stories.ts | Marks thumbnail images decorative (alt="") to satisfy a11y checks. |
| frontend/src/v2/lib/menus/RMenuItem/RMenuItem.stories.ts | Adds required role="menu" parents for menuitem-role stories. |
| frontend/src/v2/lib/forms/RTextField/RTextField.vue | Adds prependInnerLabel/appendInnerLabel to name interactive adornment buttons. |
| frontend/src/v2/lib/forms/RTextField/RTextField.stories.ts | Ensures form examples provide visible labels/tooltips needed for accessibility. |
| frontend/src/v2/lib/forms/RSwitch/RSwitch.stories.ts | Adds play() keyboard toggle coverage for switch semantics. |
| frontend/src/v2/lib/forms/RSlider/RSlider.stories.ts | Provides default and per-instance labeling guidance for an unlabeled slider UI. |
| frontend/src/v2/lib/forms/RSelect/RSelect.stories.ts | Adds an explicit a11y.test: "todo" escape hatch for the known nested-interactive issue. |
| frontend/src/v2/lib/forms/RRating/RRating.stories.ts | Adds play() keyboard navigation coverage for rating radio semantics. |
| frontend/src/v2/lib/forms/RDropzone/RDropzone.stories.ts | Supplies an input label for the underlying file input. |
| frontend/src/v2/lib/forms/RDateField/RDateField.vue | Moves role="combobox" and related ARIA attributes to a valid container to preserve labeling. |
| frontend/src/v2/lib/forms/RCheckbox/RCheckbox.stories.ts | Updates no-visible-label example to provide an accessible name. |
| frontend/src/v2/lib/data/RTable/RTable.vue | Adds table semantics and marks layout-only wrappers presentational to satisfy required ARIA structure. |
Files not reviewed (1)
- frontend/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Explain the changes or enhancements you are proposing with this pull request.
Adds an automated accessibility audit layer to the v2 UI, fixes the violations it surfaces, and adds keyboard-operability tests. Targets the unlabeled-control problem in #1848.
1. Axe wired into the vitest Storybook runner (
frontend/test/storybook.test.ts)Every v2
libstory renders into an owned canvas and is scanned byaxe-core, gated per story by the@storybook/addon-a11ya11y.testparameter:error(global default,.storybook/preview.ts) fails on any violation,todowarns without failing (a ratchet escape hatch),offskips.color-contrastis disabled in this layer (happy-dom has no layout); see follow-ups. New dependency:axe-core(devDependency).2. Keyboard-operability
play()tests added toRSwitch,RTabNav,RRating(Tab walks focus in order; Enter/Space fliparia-checked/aria-selected).3. Fixed the violations. Turning the gate on surfaced 55 failing stories. All are now resolved except one:
RToolbar,RTooltip,RCarousel,RTabNav,RCheckbox).label; rendersaria-hiddenwhen decorative (indeterminate + no label/slot), else named via label or slot content. Cascades toRSpinner,RSelect/RTextFieldloading spinners.*InnerLabelprop (falls back to the tooltip); form fields labeled.role="combobox"moved off the<label>wrapper onto the field container (restores the input's label + uses an allowed role).tableparent in every state.aria-labelthe components already support.Remaining (1
todo, intentionally deferred)RSelect(Clearable): the clear<button>is nested inside the activator<button>(nested-interactive). The correct fix lifts the clear/chip-close controls out of the activator (select-only combobox pattern), which moves the field's border/hover/focus/error chrome onto a wrapper. This is a visual-layout refactor that needs in-browser verification, so it's left as a trackedtodorather than shipped blind.Suggested follow-ups
@storybook/addon-vitestwith Playwright browser mode to also catch color contrast + focus visibility in both themes.AI-assistance disclosure
This PR was written with AI assistance (PostHog Code / Claude). All changes were reviewed; the full local test/typecheck/lint suite passes (axe runner 363, full vitest 469, vue-tsc, trunk).
Checklist
Please check all that apply.
Screenshots (if applicable)
N/A (test-tooling + a11y semantics; no visual-layout changes)
Refs #1848
Created with PostHog Code