fix(website): Improve accessibility of tabs and file upload widgets#1588
Conversation
Tab triggers in `TryIt.vue` (URL / Folder / ZIP) and `TryItResult.vue` (Result / File Selection) now expose `role="tablist"`, `role="tab"`, and `aria-selected`. Their panels carry `role="tabpanel"` plus `aria-labelledby` so screen readers can pair the active tab with its content. File and folder upload drop zones in `TryItFileUpload.vue` and `TryItFolderUpload.vue` were focusable only by mouse. They now act as real buttons (`role="button"`, `tabindex="0"`, Enter/Space activation, `aria-label`), and the inline clear buttons announce themselves via `aria-label`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR enhances accessibility across the Try It UI by adding ARIA semantics to tab navigation and keyboard support to upload interactions. Four Vue components receive targeted improvements: tab lists and buttons gain ChangesAccessibility Improvements
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
website/client/components/Home/TryItFolderUpload.vue (1)
76-111:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSame keydown-bubbling issue as
TryItFileUpload.vue.Pressing Enter/Space while focus is on the inner clear button (lines 106–111) will clear the selection and then re-trigger the folder picker because the keydown event bubbles up to the outer
role="button"container that listens for@keydown.enter.prevent/@keydown.space.prevent. Apply the same fix here (either.stopon the clear button's keydown, or.selfon the container's keydown handlers). See the matching comment onTryItFileUpload.vuefor the diff.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@website/client/components/Home/TryItFolderUpload.vue` around lines 76 - 111, The clear button's keydown events are bubbling to the outer upload-container which also listens for `@keydown.enter.prevent` and `@keydown.space.prevent`, causing the folder picker to re-open after clearing; update the clear-button element to stop propagation of keydown for Enter/Space (e.g., add keydown handlers with .stop for Enter and Space) so invoking clearFolder via the clear button doesn't bubble to triggerFileInput on the container (alternatively you can make the container keydown handlers use .self); modify the clear button near the Selected: {{ selectedFolder }} block to intercept keydown events and call clearFolder without letting them propagate.website/client/components/Home/TryIt.vue (1)
5-36:⚠️ Potential issue | 🟠 Major | ⚡ Quick winIncomplete tablist pattern — input panels are missing
role="tabpanel"and labelling.The tab buttons declare
role="tab"but the corresponding input area (.input-field, lines 38–60) that they switch between has norole="tabpanel", noid, noaria-labelledby, and the tabs have noaria-controlspointing at it. Per the WAI-ARIA tabs pattern, each tab must be associated with a tabpanel; otherwise assistive tech announces "tab" state but cannot find/describe the controlled region, which partially defeats the goal of this PR.TryItResult.vuein this same PR does this correctly and can serve as the reference.Additionally, since the three panels are rendered conditionally (
v-if/v-else-if) into a single container, the simplest fix is to give each input component (or a wrapper) a stableidper mode and addaria-controlson each tab, plusrole="tabpanel"+aria-labelledbyon the rendered panel.♻️ Sketch of the fix
<button type="button" role="tab" + id="tab-url" + aria-controls="tabpanel-url" aria-label="Remote repository URL" :aria-selected="mode === 'url'" ... > <!-- repeat id/aria-controls for folder and file tabs --> - <div class="input-field"> + <div + class="input-field" + role="tabpanel" + :id="`tabpanel-${mode}`" + :aria-labelledby="`tab-${mode}`" + >Optional follow-up: consider arrow-key navigation between tabs (roving
tabindex) per the ARIA APG tabs pattern for full keyboard parity.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@website/client/components/Home/TryIt.vue` around lines 5 - 36, The tablist lacks proper tabpanel associations: update the three tab buttons in the .tab-container (the buttons that call setMode('url'|'folder'|'file') and read mode) to include aria-controls pointing to stable IDs (e.g. input-panel-url / input-panel-folder / input-panel-file), and give each rendered input area (the .input-field wrapper or the component instance shown via v-if / v-else-if) role="tabpanel", an id matching the button's aria-controls, and aria-labelledby pointing back at the corresponding tab button id (assign predictable ids to the buttons, e.g. tab-url/tab-folder/tab-file). Use the existing mode value to compute/assign these IDs so the correct tab and panel are associated when switching.website/client/components/Home/TryItFileUpload.vue (1)
66-101:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeydown on the inner clear button bubbles to the outer drop zone and re-opens the file picker.
The outer container has
role="button"with@keydown.enter.prevent="triggerFileInput"and@keydown.space.prevent="triggerFileInput". The inner.clear-button(lines 96–101) uses@click.stopto block the click bubble, but the keydown event is not stopped. When focus is on the clear button and the user presses Enter or Space, the browser firesclickon the clear button (clearing the file) and the keydown bubbles up to the container, which immediately callstriggerFileInput()and opens the file picker — the opposite of what the user intended.Also worth noting: nesting a real
<button>(and an<input>) inside an element withrole="button"is an invalid ARIA nesting (interactive descendants of a button role), which some screen readers handle inconsistently. A cleaner long-term fix is to make the drop zone a non-button element with explicit click/keydown handling, or scope the keyboard activation to only fire when the event target is the container itself.🛡️ Minimal fix: stop keydown propagation on the clear button
<button type="button" class="clear-button" aria-label="Clear selected file" `@click.stop`="clearFile" + `@keydown.enter.stop` + `@keydown.space.stop` >×</button>Or, on the outer container, ignore activation when the event originated from a descendant:
- `@keydown.enter.prevent`="triggerFileInput" - `@keydown.space.prevent`="triggerFileInput" + `@keydown.enter.self.prevent`="triggerFileInput" + `@keydown.space.self.prevent`="triggerFileInput"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@website/client/components/Home/TryItFileUpload.vue` around lines 66 - 101, The Enter/Space keydown on the inner clear button bubbles to the outer drop zone and triggers triggerFileInput; fix by preventing keydown propagation on the clear button (add a keydown handler on the element with class "clear-button" that stops propagation and prevents default for Enter/Space) or, alternatively, make the outer handler (the element with class "upload-container") only activate when the event.target === event.currentTarget in its keydown handlers; update handlers around triggerFileInput, clearFile, and the clear-button to implement one of these approaches and ensure accessibility semantics remain correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@website/client/components/Home/TryIt.vue`:
- Around line 5-36: The tablist lacks proper tabpanel associations: update the
three tab buttons in the .tab-container (the buttons that call
setMode('url'|'folder'|'file') and read mode) to include aria-controls pointing
to stable IDs (e.g. input-panel-url / input-panel-folder / input-panel-file),
and give each rendered input area (the .input-field wrapper or the component
instance shown via v-if / v-else-if) role="tabpanel", an id matching the
button's aria-controls, and aria-labelledby pointing back at the corresponding
tab button id (assign predictable ids to the buttons, e.g.
tab-url/tab-folder/tab-file). Use the existing mode value to compute/assign
these IDs so the correct tab and panel are associated when switching.
In `@website/client/components/Home/TryItFileUpload.vue`:
- Around line 66-101: The Enter/Space keydown on the inner clear button bubbles
to the outer drop zone and triggers triggerFileInput; fix by preventing keydown
propagation on the clear button (add a keydown handler on the element with class
"clear-button" that stops propagation and prevents default for Enter/Space) or,
alternatively, make the outer handler (the element with class
"upload-container") only activate when the event.target === event.currentTarget
in its keydown handlers; update handlers around triggerFileInput, clearFile, and
the clear-button to implement one of these approaches and ensure accessibility
semantics remain correct.
In `@website/client/components/Home/TryItFolderUpload.vue`:
- Around line 76-111: The clear button's keydown events are bubbling to the
outer upload-container which also listens for `@keydown.enter.prevent` and
`@keydown.space.prevent`, causing the folder picker to re-open after clearing;
update the clear-button element to stop propagation of keydown for Enter/Space
(e.g., add keydown handlers with .stop for Enter and Space) so invoking
clearFolder via the clear button doesn't bubble to triggerFileInput on the
container (alternatively you can make the container keydown handlers use .self);
modify the clear button near the Selected: {{ selectedFolder }} block to
intercept keydown events and call clearFolder without letting them propagate.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3d42f420-00b8-438c-b766-99124e38fe10
📒 Files selected for processing (4)
website/client/components/Home/TryIt.vuewebsite/client/components/Home/TryItFileUpload.vuewebsite/client/components/Home/TryItFolderUpload.vuewebsite/client/components/Home/TryItResult.vue
There was a problem hiding this comment.
Code Review
This pull request improves the accessibility of the 'Try It' components by adding ARIA roles, labels, and keyboard event handlers. Feedback highlights that the tab implementation in TryIt.vue is incomplete compared to TryItResult.vue, missing proper aria-controls and tabpanel associations. Furthermore, the file and folder upload components need @keydown.stop on their clear buttons to prevent event bubbling to the parent container, and the nested interactive control structure should be addressed to comply with accessibility standards.
Review SummarySolid accessibility improvement — adds the right ARIA semantics for tabs/tabpanels and makes the drop zones keyboard-operable. Net positive. A few follow-ups worth considering before/after merge. DetailsRecommended1. Add a visible focus indicator (WCAG 2.4.7) The drop zones now receive keyboard focus ( Suggested additions: ```css /* TryItFileUpload.vue + TryItFolderUpload.vue */ /* TryItResult.vue */ 2. Tab keyboard navigation (WAI-ARIA APG) The WAI-ARIA Authoring Practices tab pattern expects Left/Right arrow navigation between tabs plus a roving `tabindex` (active tab `tabindex="0"`, inactive tabs `tabindex="-1"`). Right now all three tabs are in the tab sequence and arrow keys do nothing. Reasonable to defer to a follow-up PR — calling it out so it's tracked. Nit3. Static IDs in `id="tab-result"` / `id="tabpanel-result"` etc. are fine as long as the component mounts at most once per page (currently true). If `TryItResult` ever renders multiple times, the IDs would collide and break `aria-labelledby` / `aria-controls`. Easy to make instance-unique later with `useId()` (Vue 3.5+) or a local counter. Not blocking
|
⚡ Performance Benchmark
Details
Historyd60b100 fix(website): Improve accessibility of tabs and file upload widgets
|
- Scope upload-zone keyboard activation to the container itself via `@keydown.enter.self.prevent` / `@keydown.space.self.prevent`. The previous handlers fired even when the inner clear button was focused, re-opening the picker after a clear. - Add tabpanel semantics to the URL/Folder/ZIP input area in `TryIt.vue` so the mode tabs now point at a single `tabpanel-input` region with `aria-labelledby` derived from the active mode, mirroring the existing pattern in `TryItResult.vue`. - Add `:focus-visible` outlines to the tab buttons and upload drop zones in all four files so keyboard users get a clear focus indicator (WCAG 2.4.7). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-merge review notesThanks for landing the a11y improvements! Since this is already merged, sharing findings as follow-up suggestions rather than blockers. Six reviewers (code-quality, security, performance, test-coverage, conventions, holistic a11y) looked at the diff — no security or performance concerns. A few items worth a follow-up: Worth a follow-up1. Tabs are missing arrow-key navigation (P1, a11y) — 2. Interactive descendants nested inside 3. No live-region announcement on selection/error (P2, a11y) — When 4. Inconsistent tabpanel-id strategy within the same PR — Nits / smaller items
Already verified clean
🤖 Generated with Claude Code |
Summary
role="tablist"/role="tab"/aria-selectedto the URL/Folder/ZIP mode tabs inTryIt.vueand the Result/File Selection tabs inTryItResult.vue.role="tabpanel"+aria-labelledbyso assistive tech can pair the active tab with its content.role="button",tabindex="0", Enter/Space activation, and anaria-label.aria-labelto the inline clear (×) button for the selected file/folder.Why
Tab triggers had no role or selected-state hints, so screen reader users could not tell which view was active. The upload drop zones were a
divover adisplay:noneinput, which made the picker unreachable by keyboard and invisible to screen readers.Checklist
node --run lint(website/client)node --run docs:build(website/client)