Skip to content

a11y: screen reader and keyboard improvements#243

Open
ways2read wants to merge 2 commits intojamiepine:mainfrom
ways2read:a11y/screen-reader-and-keyboard-improvements
Open

a11y: screen reader and keyboard improvements#243
ways2read wants to merge 2 commits intojamiepine:mainfrom
ways2read:a11y/screen-reader-and-keyboard-improvements

Conversation

@ways2read
Copy link

@ways2read ways2read commented Mar 7, 2026

  • Audio player: aria-labels for Play/Pause, Loop, Mute, Close; labelled playback and volume sliders
  • Generation: aria-labels for Generate speech and Fine tune instructions buttons
  • Voice cards: focusable, labelled, Enter/Space to select
  • History rows: focusable, labelled, Enter/Space to play; transcript textarea labelled
  • Voices tab: focusable rows, labelled, Enter/Space to edit; Actions button labelled
  • Model management: focusable model rows and labelled Download/Delete buttons
  • Server tab: regions with aria-label and tabIndex for Connection, Status, App Updates
  • Stories: focusable story rows, labelled, Enter/Space to select; Actions and track editor buttons labelled
  • Voice profile samples: Play/Pause/Stop and mini-player slider labelled

Tested with NVDA and Narrator on Windows. See docs/PR-ACCESSIBILITY.md for full description.

Summary by CodeRabbit

  • New Features
    • Expanded accessibility across the app: screen-reader-friendly labels, roles, and focusability for audio controls, generation UI, voice profiles, history, models, server status, and stories.
    • Improved keyboard navigation and activation (Enter/Space) for lists, rows, buttons, and playback controls.
    • Better descriptive feedback for play/pause, volume, progress/seek, download/delete actions, and selected states.
  • Documentation
    • Added comprehensive accessibility guidance and testing notes.

- Audio player: aria-labels for Play/Pause, Loop, Mute, Close; labelled playback and volume sliders
- Generation: aria-labels for Generate speech and Fine tune instructions buttons
- Voice cards: focusable, labelled, Enter/Space to select
- History rows: focusable, labelled, Enter/Space to play; transcript textarea labelled
- Voices tab: focusable rows, labelled, Enter/Space to edit; Actions button labelled
- Model management: focusable model rows and labelled Download/Delete buttons
- Server tab: regions with aria-label and tabIndex for Connection, Status, App Updates
- Stories: focusable story rows, labelled, Enter/Space to select; Actions and track editor buttons labelled
- Voice profile samples: Play/Pause/Stop and mini-player slider labelled

Tested with NVDA and Narrator on Windows. See docs/PR-ACCESSIBILITY.md for full description.

Made-with: Cursor
@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 044cc5a2-5234-41d5-a6c0-628a0a3c5e2b

📥 Commits

Reviewing files that changed from the base of the PR and between 19a28bf and 9955e1d.

📒 Files selected for processing (5)
  • app/src/components/History/HistoryTable.tsx
  • app/src/components/StoriesTab/StoryList.tsx
  • app/src/components/VoiceProfiles/ProfileCard.tsx
  • app/src/components/VoicesTab/VoicesTab.tsx
  • docs/PR-ACCESSIBILITY.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/PR-ACCESSIBILITY.md
  • app/src/components/VoiceProfiles/ProfileCard.tsx

📝 Walkthrough

Walkthrough

Adds accessibility attributes and keyboard interactions across the app: ARIA labels/roles, tabIndex, aria-valuetext, and onKeyDown handlers for playback, generation, history, voices, stories, server settings, and model management. No exported APIs or core control flow were changed.

Changes

Cohort / File(s) Summary
Audio player & samples
app/src/components/AudioPlayer/AudioPlayer.tsx, app/src/components/VoiceProfiles/AudioSampleRecording.tsx, app/src/components/VoiceProfiles/AudioSampleSystem.tsx, app/src/components/VoiceProfiles/AudioSampleUpload.tsx, app/src/components/VoiceProfiles/SampleList.tsx
Added useId-based labeling, aria-labels for play/pause/mute/unmute/close, aria-valuetext for sliders (progress/volume), and role/group semantics for volume controls. Accessibility metadata only.
Generation UI
app/src/components/Generation/FloatingGenerateBox.tsx
Generate button and instruct-mode toggle now expose dynamic aria-labels reflecting pending state, missing profile, or ready state.
Focusable rows & cards
app/src/components/History/HistoryTable.tsx, app/src/components/StoriesTab/StoryList.tsx, app/src/components/VoicesTab/VoicesTab.tsx, app/src/components/VoiceProfiles/ProfileCard.tsx
Converted items to keyboard-focusable elements (role="button", tabIndex), added aria-label/aria-pressed, onKeyDown handlers for Enter/Space, and refined click/selection handling to avoid text-selection or textarea conflicts.
Story editor toolbar
app/src/components/StoriesTab/StoryTrackEditor.tsx
Added aria-labels to toolbar actions (play/pause, stop, split, duplicate, delete, zoom in/out) and adjusted button elements to include these attributes without modifying behavior.
Server settings & model management
app/src/components/ServerSettings/ConnectionForm.tsx, app/src/components/ServerSettings/ModelManagement.tsx, app/src/components/ServerSettings/ServerStatus.tsx, app/src/components/ServerSettings/UpdateStatus.tsx
Cards made into accessible regions (role="region", aria-label, tabIndex). Model rows and action buttons receive status-aware aria-labels (delete/unload, download, downloading). No data-flow changes.
Documentation
docs/PR-ACCESSIBILITY.md
New documentation summarizing accessibility changes, testing guidance, and implementation notes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code with gentle feet,

Added labels so each control can speak,
Enter, Space — now all can play,
Screen readers hum and find their way,
A tiny carrot for access today. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'a11y: screen reader and keyboard improvements' accurately and concisely describes the main objective of the pull request across all modified components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
app/src/components/Generation/FloatingGenerateBox.tsx (1)

335-353: Consider using aria-pressed for toggle state.

The current label approach works, but for toggle buttons, aria-pressed is the semantic standard that screen readers specifically announce as "pressed/not pressed."

♻️ Optional: Use aria-pressed for toggle semantics
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          onClick={() => setIsInstructMode(!isInstructMode)}
                          className={cn(
                            'h-10 w-10 rounded-full transition-all duration-200',
                            isInstructMode
                              ? 'bg-accent text-accent-foreground border border-accent hover:bg-accent/90'
                              : 'bg-card border border-border hover:bg-background/50',
                          )}
-                          aria-label={
-                            isInstructMode
-                              ? 'Fine tune instructions, on'
-                              : 'Fine tune instructions'
-                          }
+                          aria-label="Fine tune instructions"
+                          aria-pressed={isInstructMode}
                        >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/Generation/FloatingGenerateBox.tsx` around lines 335 -
353, The toggle Button in FloatingGenerateBox.tsx currently uses aria-label to
indicate state; update the Button element (the component rendering
SlidersHorizontal and using isInstructMode and setIsInstructMode) to include the
boolean aria-pressed={isInstructMode} so screen readers get proper toggle
semantics, while keeping or adjusting aria-label if desired; ensure the onClick
still calls setIsInstructMode(!isInstructMode) and that the aria-pressed value
is derived from the same isInstructMode state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/components/History/HistoryTable.tsx`:
- Around line 275-282: The row-level onKeyDown handler currently only ignores
textarea children so Enter/Space pressed on action controls still triggers
handlePlay; update the guard in that onKeyDown to detect and return early when
the event target is inside any interactive child (e.g., closest('input, button,
select, textarea, a, [role="button"]') or a custom marker like
[data-ignore-row-shortcut]) so keyboard interactions on child controls don’t
bubble to the row and call handlePlay(gen.id, gen.text, gen.profile_id).

In `@app/src/components/StoriesTab/StoryList.tsx`:
- Around line 197-211: The row is keyboard-selectable but lacks programmatic
state for assistive tech; update the element in StoryList.tsx (the container
using role="button", selectedStoryId and setSelectedStoryId) to expose the
active story—add an appropriate ARIA attribute such as
aria-pressed={selectedStoryId === story.id} or aria-current={selectedStoryId ===
story.id} (or convert to listbox/option roles if you prefer that pattern), and
ensure the attribute updates when setSelectedStoryId is called so screen readers
can announce which story is selected.

In `@app/src/components/VoiceProfiles/ProfileCard.tsx`:
- Around line 64-69: The onKeyDown handler in ProfileCard (handleKeyDown) is
firing for nested action controls (Export/Edit/Delete) because the event
bubbles; update handleKeyDown to ignore events originating from interactive
descendants by checking the event target (e.g., if (e.target as
HTMLElement).closest('button, a, [role="button"], input, textarea, select,
[tabindex]') return;) before calling e.preventDefault() and handleSelect();
apply the same guard to the similar handler used around lines 82-87.

In `@app/src/components/VoicesTab/VoicesTab.tsx`:
- Around line 198-205: Remove the ARIA role on the table row: do not set
role="button" on the TableRow (which renders a <tr>) and instead place an
interactive element inside a cell; keep the existing onClick/onKeyDown logic but
move the clickable target into a focusable control (e.g., a <button> or <a>
inside the relevant TableCell) that calls onEdit and uses handleKeyDown, and use
rowLabel for the inner control's aria-label so table semantics remain intact
while preserving keyboard accessibility.

In `@docs/PR-ACCESSIBILITY.md`:
- Line 21: Update the two bullets to use the requested wording: change the
phrase "Fine tune instructions (sliders)" to "Fine-tune instructions (sliders)"
and change the other bullet's phrasing to include "focus on the text area"
(replace the existing wording on that line with a variant that says e.g. "when
opened, focus on the text area"). Locate the lines containing the current
bullets (the one mentioning "Generate speech (submit) and Fine tune instructions
(sliders) – Icon buttons now have `aria-label`..." and the other bullet around
the sentence that should become "focus on the text area") and replace the
phrasing accordingly.

---

Nitpick comments:
In `@app/src/components/Generation/FloatingGenerateBox.tsx`:
- Around line 335-353: The toggle Button in FloatingGenerateBox.tsx currently
uses aria-label to indicate state; update the Button element (the component
rendering SlidersHorizontal and using isInstructMode and setIsInstructMode) to
include the boolean aria-pressed={isInstructMode} so screen readers get proper
toggle semantics, while keeping or adjusting aria-label if desired; ensure the
onClick still calls setIsInstructMode(!isInstructMode) and that the aria-pressed
value is derived from the same isInstructMode state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fcc3ff3-64b9-4a3e-aed4-21ca59cfd26a

📥 Commits

Reviewing files that changed from the base of the PR and between 38bf96f and 19a28bf.

📒 Files selected for processing (16)
  • app/src/components/AudioPlayer/AudioPlayer.tsx
  • app/src/components/Generation/FloatingGenerateBox.tsx
  • app/src/components/History/HistoryTable.tsx
  • app/src/components/ServerSettings/ConnectionForm.tsx
  • app/src/components/ServerSettings/ModelManagement.tsx
  • app/src/components/ServerSettings/ServerStatus.tsx
  • app/src/components/ServerSettings/UpdateStatus.tsx
  • app/src/components/StoriesTab/StoryList.tsx
  • app/src/components/StoriesTab/StoryTrackEditor.tsx
  • app/src/components/VoiceProfiles/AudioSampleRecording.tsx
  • app/src/components/VoiceProfiles/AudioSampleSystem.tsx
  • app/src/components/VoiceProfiles/AudioSampleUpload.tsx
  • app/src/components/VoiceProfiles/ProfileCard.tsx
  • app/src/components/VoiceProfiles/SampleList.tsx
  • app/src/components/VoicesTab/VoicesTab.tsx
  • docs/PR-ACCESSIBILITY.md

Comment on lines +275 to +282
onKeyDown={(e) => {
const target = e.target as HTMLElement;
if (target.closest('textarea')) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handlePlay(gen.id, gen.text, gen.profile_id);
}
}}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Skip the row shortcut when focus is inside child controls.

onKeyDown only excludes the transcript, so Enter/Space on the Actions trigger will also play or restart the sample. The mouse path is protected with stopPropagation(), but the keyboard path still bubbles to the row handler.

💡 Minimal guard
                  onKeyDown={(e) => {
-                   const target = e.target as HTMLElement;
-                   if (target.closest('textarea')) return;
+                   if (e.target !== e.currentTarget) return;
                    if (e.key === 'Enter' || e.key === ' ') {
                      e.preventDefault();
                      handlePlay(gen.id, gen.text, gen.profile_id);
                    }
                  }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onKeyDown={(e) => {
const target = e.target as HTMLElement;
if (target.closest('textarea')) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handlePlay(gen.id, gen.text, gen.profile_id);
}
}}
onKeyDown={(e) => {
if (e.target !== e.currentTarget) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handlePlay(gen.id, gen.text, gen.profile_id);
}
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/History/HistoryTable.tsx` around lines 275 - 282, The
row-level onKeyDown handler currently only ignores textarea children so
Enter/Space pressed on action controls still triggers handlePlay; update the
guard in that onKeyDown to detect and return early when the event target is
inside any interactive child (e.g., closest('input, button, select, textarea, a,
[role="button"]') or a custom marker like [data-ignore-row-shortcut]) so
keyboard interactions on child controls don’t bubble to the row and call
handlePlay(gen.id, gen.text, gen.profile_id).

Comment on lines +64 to +69
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSelect();
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep row-level key handling off the nested action buttons.

Because onKeyDown is attached to the whole card, pressing Enter/Space on Export/Edit/Delete will bubble up and also run handleSelect(). That makes the inner actions unexpectedly select/deselect the profile for keyboard users.

💡 Minimal guard
  const handleKeyDown = (e: React.KeyboardEvent) => {
+   if (e.target !== e.currentTarget) {
+     return;
+   }
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleSelect();
    }
  };

Also applies to: 82-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/VoiceProfiles/ProfileCard.tsx` around lines 64 - 69, The
onKeyDown handler in ProfileCard (handleKeyDown) is firing for nested action
controls (Export/Edit/Delete) because the event bubbles; update handleKeyDown to
ignore events originating from interactive descendants by checking the event
target (e.g., if (e.target as HTMLElement).closest('button, a, [role="button"],
input, textarea, select, [tabindex]') return;) before calling e.preventDefault()
and handleSelect(); apply the same guard to the similar handler used around
lines 82-87.

- HistoryTable: skip row key handler when focus is on Actions button (Enter/Space)
- StoryList: expose selected story (aria-pressed, 'Selected' in label)
- ProfileCard: skip card key handler when focus is on Export/Edit/Delete
- VoicesTab: keep table semantics; edit button in first cell instead of role=button on row
- PR-ACCESSIBILITY.md: 'Fine-tune' wording, 'focus on the text area' phrasing

Made-with: Cursor
@Vishaldadlani321
Copy link

add hindi language

@ways2read
Copy link
Author

ways2read commented Mar 9, 2026 via email

@Vishaldadlani321
Copy link

voice box mein audio generate nahi kar paa raha hai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants