Skip to content

Night Shift: fix a11y Space key video toggle#19

Open
EtanHey wants to merge 1 commit intomasterfrom
nightshift/2026-02-23-6011
Open

Night Shift: fix a11y Space key video toggle#19
EtanHey wants to merge 1 commit intomasterfrom
nightshift/2026-02-23-6011

Conversation

@EtanHey
Copy link
Owner

@EtanHey EtanHey commented Feb 23, 2026

User description

Automated improvement by Golems Night Shift.

fix a11y Space key video toggle


PR Type

Bug fix, Enhancement


Description

  • Add Space key support to video toggle button for WCAG compliance

  • Prevent default page scroll behavior when Space key is pressed

  • Improve keyboard accessibility for mobile video collapse feature


Diagram Walkthrough

flowchart LR
  A["Video Toggle Button"] -->|"Previously: Enter only"| B["Limited Keyboard Access"]
  A -->|"Now: Enter + Space"| C["Full WCAG Compliance"]
  D["Space Key Press"] -->|"preventDefault()"| E["No Page Scroll"]
Loading

File Walkthrough

Relevant files
Bug fix
song.$songId.tsx
Add Space key handling to video toggle button                       

src/routes/song.$songId.tsx

  • Enhanced onKeyDown handler to accept both Enter and Space keys
  • Added e.preventDefault() to prevent page scrolling on Space press
  • Maintains existing Enter key functionality while adding Space support
  • Improves keyboard accessibility for mobile video collapse toggle
+6/-1     

Note

Low Risk
Small, localized UI a11y change limited to a keyboard event handler; minimal behavioral risk beyond key interaction.

Overview
Improves keyboard accessibility for the mobile video section’s collapsible header in src/routes/song.$songId.tsx.

The header’s onKeyDown handler now toggles on both Enter and Space, and calls preventDefault() for Space to avoid page scrolling while activating the collapse/expand behavior.

Written by Cursor Bugbot for commit 7281d75. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved keyboard accessibility for the mobile video header toggle, which now responds to both Enter and Space keys for collapsing and expanding the video player.

Elements with role="button" must handle both Enter and Space keys per
WCAG 2.1 keyboard accessibility guidelines. The mobile video collapse
toggle only responded to Enter, leaving keyboard-only users unable to
activate it with Space. Also adds preventDefault() to avoid page
scrolling on Space press.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
songscript Ready Ready Preview, Comment Feb 23, 2026 2:07am

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

The pull request enhances keyboard accessibility for the mobile video header by adding Space key support alongside the existing Enter key handling in the header toggle mechanism, enabling users to collapse or expand the video using either key.

Changes

Cohort / File(s) Summary
Keyboard Accessibility Enhancement
src/routes/song.$songId.tsx
Expanded key handling in the video header toggle to respond to both Enter and Space keys, each triggering the video collapse/expand toggle with event prevention.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A hop, a skip, a spacebar press,
The video toggles—oh, what a fest!
Enter and Space now work in harmony,
Mobile accessibility, wild and free!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly and specifically describes the main change: adding Space key accessibility support to the video toggle component, which directly matches the changeset focus on enhancing keyboard accessibility.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nightshift/2026-02-23-6011

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.

❤️ Share

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

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Add aria-expanded attribute

Add the aria-expanded attribute to the div with role="button" to inform screen
readers about the collapsed or expanded state of the video section.

src/routes/song.$songId.tsx [701-712]

 <div
   role="button"
   tabIndex={0}
+  aria-expanded={!videoCollapsed}
   onClick={handleVideoCollapsedToggle}
   onKeyDown={...}
   className="w-full flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700 hover:bg-gray-700/50 transition-colors cursor-pointer"
 >

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This is a critical accessibility improvement. Adding aria-expanded to an element with role="button" that controls a collapsible section is essential for screen reader users to understand the state of the UI.

Medium
Align spacebar behavior with accessibility standards

To improve accessibility, modify the event handling to trigger the toggle action
on keyUp for the spacebar, while keeping the keyDown logic for the 'Enter' key
and to prevent default browser behavior.

src/routes/song.$songId.tsx [705-710]

 onKeyDown={(e) => {
-  if (e.key === 'Enter' || e.key === ' ') {
+  // Prevent scrolling on spacebar press
+  if (e.key === ' ') {
+    e.preventDefault();
+  }
+  if (e.key === 'Enter') {
+    e.preventDefault();
+    handleVideoCollapsedToggle();
+  }
+}}
+onKeyUp={(e) => {
+  // Trigger on spacebar release
+  if (e.key === ' ') {
     e.preventDefault();
     handleVideoCollapsedToggle();
   }
 }}
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valuable accessibility improvement, correctly pointing out that button actions for the spacebar should trigger on keyUp to allow cancellation, which aligns with WAI-ARIA best practices.

Medium
  • More

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/routes/song.$songId.tsx (2)

671-671: 🧹 Nitpick | 🔵 Trivial

Inconsistent Space key detection: event.code === 'Space' (line 671) vs e.key === ' ' (line 706).

Both detect the Space key on standard keyboards, but mixing code (physical key position) and key (character value) within the same file is inconsistent. Using e.key === ' ' (as in the new handler) is the more semantically correct choice for character-intent checks; consider aligning the global handler at line 671 to match.

♻️ Align global handler to use `e.key`
-      if (event.code === 'Space' && event.target === document.body) {
+      if (event.key === ' ' && event.target === document.body) {

Also applies to: 706-706

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

In `@src/routes/song`.$songId.tsx at line 671, The global key handler currently
checks the Space key using event.code === 'Space' while another handler uses
e.key === ' ' — make them consistent by switching the global check to use
event.key === ' ' (and keep the existing document.body target check) so both
handlers detect character-intent Space uniformly; update the conditional in the
global handler (the function that contains event.code === 'Space') to use
event.key === ' ' and verify associated logic still behaves as expected.

701-711: ⚠️ Potential issue | 🟡 Minor

role="button" is missing aria-expanded — screen readers cannot convey collapsed/expanded state.

A role="button" that toggles visibility should expose aria-expanded per WCAG 2.1 SC 4.1.2. Without it, AT users pressing the button have no programmatic feedback about whether the video is currently collapsed or expanded.

♿ Proposed fix
               <div
                 role="button"
                 tabIndex={0}
+                aria-expanded={!isVideoCollapsed}
+                aria-label={isVideoCollapsed ? "Expand video" : "Collapse video"}
                 onClick={handleVideoCollapsedToggle}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/song`.$songId.tsx around lines 701 - 711, The button-like div with
role="button" that calls handleVideoCollapsedToggle must expose its current
expanded state via aria-expanded; add an aria-expanded attribute bound to the
boolean state that tracks whether the video is collapsed/expanded (the same
state used by handleVideoCollapsedToggle, e.g., isVideoCollapsed or
videoCollapsed) on the element so screen readers can announce the
collapsed/expanded state and keep role="button" and keyboard handlers unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a964b0 and 7281d75.

📒 Files selected for processing (1)
  • src/routes/song.$songId.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TanStack Start framework with Bun runtime for the application

Tests must pass locally via bun run test before committing code, as Husky pre-commit hooks will block commits with failing tests

Files:

  • src/routes/song.$songId.tsx
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Components with logic MUST have corresponding test files following the naming convention ComponentName.test.tsx

Files:

  • src/routes/song.$songId.tsx
🔇 Additional comments (1)
src/routes/song.$songId.tsx (1)

705-710: ⚠️ Potential issue | 🟡 Minor

Add behavioral tests for the new keyboard interaction logic to src/__tests__/songPageDefaults.test.ts.

While a test file exists for the song page, it only performs static file-content checks. The new onKeyDown handler with Space and Enter key support lacks behavioral test coverage for: (a) Space key toggling the collapse, (b) Enter key toggling the collapse, (c) preventDefault preventing default scroll behavior, (d) unrelated keys being ignored. Extend the existing integration test to cover these keyboard interaction scenarios.

⛔ Skipped due to learnings
Learnt from: CR
Repo: EtanHey/songscript PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-28T12:17:53.877Z
Learning: Applies to **/*.tsx : Components with logic MUST have corresponding test files following the naming convention `ComponentName.test.tsx`
Learnt from: CR
Repo: EtanHey/songscript PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-28T12:17:53.877Z
Learning: Applies to **/*.ts : All new helpers and utilities MUST have corresponding test files
Learnt from: CR
Repo: EtanHey/songscript PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-28T12:17:53.877Z
Learning: Applies to **/*.test.tsx : Component tests should be located in the same directory as the component file with naming convention `ComponentName.test.tsx`
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/song`.$songId.tsx:
- Around line 705-710: The keyboard handler on the role="button" wrapper is
missing a guard and aria state: update the onKeyDown handler (the arrow function
that calls handleVideoCollapsedToggle) to first check that e.target ===
e.currentTarget before calling e.preventDefault() or handleVideoCollapsedToggle
so events from nested interactive elements like ToggleGroupItem or
WishlistButton don't bubble and trigger the toggle; also add aria-expanded on
the role="button" div and bind it to the component's collapsed state (the same
state used by handleVideoCollapsedToggle) so screen readers know the
expanded/collapsed status; finally add a corresponding test file
(song.$songId.test.tsx) to cover keyboard interaction and nesting cases.

---

Outside diff comments:
In `@src/routes/song`.$songId.tsx:
- Line 671: The global key handler currently checks the Space key using
event.code === 'Space' while another handler uses e.key === ' ' — make them
consistent by switching the global check to use event.key === ' ' (and keep the
existing document.body target check) so both handlers detect character-intent
Space uniformly; update the conditional in the global handler (the function that
contains event.code === 'Space') to use event.key === ' ' and verify associated
logic still behaves as expected.
- Around line 701-711: The button-like div with role="button" that calls
handleVideoCollapsedToggle must expose its current expanded state via
aria-expanded; add an aria-expanded attribute bound to the boolean state that
tracks whether the video is collapsed/expanded (the same state used by
handleVideoCollapsedToggle, e.g., isVideoCollapsed or videoCollapsed) on the
element so screen readers can announce the collapsed/expanded state and keep
role="button" and keyboard handlers unchanged.

Comment on lines +705 to +710
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleVideoCollapsedToggle();
}
}}
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

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its size
wc -l src/routes/song.$songId.tsx

Repository: EtanHey/songscript

Length of output: 113


🏁 Script executed:

# Read the onKeyDown handler and surrounding context (lines 695-750)
sed -n '695,750p' src/routes/song.$songId.tsx | cat -n

Repository: EtanHey/songscript

Length of output: 125


🏁 Script executed:

# Check for test file for this component
find . -name "*song*test*" -o -name "*test*song*" | head -20

Repository: EtanHey/songscript

Length of output: 102


🏁 Script executed:

# Search for stopPropagation in the tsx files to understand propagation handling
rg "stopPropagation" --type tsx -B2 -A2 | head -100

Repository: EtanHey/songscript

Length of output: 89


🏁 Script executed:

# Check the global Space handler implementation (around lines 669-679)
sed -n '665,685p' src/routes/song.$songId.tsx | cat -n

Repository: EtanHey/songscript

Length of output: 125


🏁 Script executed:

# Find the actual song route file
find . -path "*/routes/*song*" -type f | head -20

Repository: EtanHey/songscript

Length of output: 91


🏁 Script executed:

# List route files to see naming pattern
ls -la src/routes/ | head -20

Repository: EtanHey/songscript

Length of output: 724


🏁 Script executed:

# Read the file with proper escaping - use single quotes to prevent variable expansion
wc -l './src/routes/song.$songId.tsx'

Repository: EtanHey/songscript

Length of output: 95


🏁 Script executed:

# Read the onKeyDown handler and surrounding context (lines 695-750)
sed -n '695,750p' './src/routes/song.$songId.tsx' | cat -n

Repository: EtanHey/songscript

Length of output: 3641


🏁 Script executed:

# Check the global Space handler implementation (around lines 665-685)
sed -n '665,685p' './src/routes/song.$songId.tsx' | cat -n

Repository: EtanHey/songscript

Length of output: 887


🏁 Script executed:

# Search for aria-expanded in the file
rg "aria-expanded" './src/routes/song.$songId.tsx' -n

Repository: EtanHey/songscript

Length of output: 44


🏁 Script executed:

# Check wrapper divs at lines 719-746 and their stopPropagation calls
sed -n '700,746p' './src/routes/song.$songId.tsx' | cat -n

Repository: EtanHey/songscript

Length of output: 3158


🏁 Script executed:

# Check for test file for song.$songId.tsx with different naming patterns
find . -name "*song*test*" -o -name "*test*song*" -o -path "*__tests__*song*" 2>/dev/null

Repository: EtanHey/songscript

Length of output: 102


🏁 Script executed:

# Check if there's a test file matching the component filename exactly
ls -la './src/routes/song.$songId.test.tsx' 2>&1

Repository: EtanHey/songscript

Length of output: 143


🏁 Script executed:

# Search for any onKeyDown stopPropagation in the codebase to see if it's a pattern
rg "onKeyDown" --type tsx -A3 | grep -A3 "stopPropagation"

Repository: EtanHey/songscript

Length of output: 89


🏁 Script executed:

# Verify ToggleGroup and WishlistButton implementations - check if they handle keydown internally
rg "WishlistButton\|ToggleGroup" './src/routes/song.$songId.tsx' -B2 -A5

Repository: EtanHey/songscript

Length of output: 44


🏁 Script executed:

# Check what library provides ToggleGroup to understand keydown behavior
rg "from.*ToggleGroup\|import.*ToggleGroup" --type tsx

Repository: EtanHey/songscript

Length of output: 89


🏁 Script executed:

# Search for ToggleGroup import
grep -n "ToggleGroup" './src/routes/song.$songId.tsx' | head -10

Repository: EtanHey/songscript

Length of output: 928


🏁 Script executed:

# Search for imports at the top of the file
head -50 './src/routes/song.$songId.tsx' | cat -n

Repository: EtanHey/songscript

Length of output: 2532


🏁 Script executed:

# Check if WishlistButton or ToggleGroup stop keydown propagation by searching their source files
find ./src -name "WishlistButton*" -o -name "*ToggleGroup*" -o -name "*wishlist*" | head -20

Repository: EtanHey/songscript

Length of output: 97


🏁 Script executed:

# Look for test files in the codebase to understand the pattern
find ./src -name "*.test.ts" -o -name "*.test.tsx" | head -20

Repository: EtanHey/songscript

Length of output: 457


🏁 Script executed:

# Check the ToggleGroup implementation to see if it stops keydown propagation
cat -n './src/components/ui/toggle-group.tsx'

Repository: EtanHey/songscript

Length of output: 2956


🏁 Script executed:

# Check WishlistButton to see if it handles keydown
cat -n './src/components/WishlistButton.tsx'

Repository: EtanHey/songscript

Length of output: 6003


🏁 Script executed:

# Verify the exact line numbers in the original file
grep -n "onKeyDown" './src/routes/song.$songId.tsx' | head -5

Repository: EtanHey/songscript

Length of output: 101


🏁 Script executed:

# Check if this component file should have a test based on coding guidelines
# Verify the test naming convention used in existing test files
ls -la './src/components/'*.test.tsx | head -10

Repository: EtanHey/songscript

Length of output: 330


🏁 Script executed:

# Verify the behavior of ToggleGroupItem keydown by checking Radix UI behavior
# See if it implements any keydown handling that might stop propagation
rg "ToggleGroupItem\|ToggleGroupPrimitive" './src/components/ui/toggle-group.tsx' -A10 | head -40

Repository: EtanHey/songscript

Length of output: 44


🏁 Script executed:

# Check if there's any keydown stopPropagation in the entire route file
grep -n "stopPropagation" './src/routes/song.$songId.tsx'

Repository: EtanHey/songscript

Length of output: 234


🏁 Script executed:

# Check if the Radix UI ToggleGroupItem handles keydown internally and whether it stops propagation
# Look at how Radix UI typically handles Space/Enter on toggle buttons
web_search "Radix UI ToggleGroup keydown event propagation space enter"

Repository: EtanHey/songscript

Length of output: 112


🏁 Script executed:

# Verify aria-expanded is indeed missing from the role="button" div
grep -n "aria-expanded" './src/routes/song.$songId.tsx'

Repository: EtanHey/songscript

Length of output: 44


🏁 Script executed:

# Confirm coding guideline: check if route page components are typically tested
find ./src/routes -name "*.test.tsx" -o -name "*.test.ts"

Repository: EtanHey/songscript

Length of output: 44


🏁 Script executed:

# Double-check the exact content and structure around the onKeyDown to confirm line numbers in review
sed -n '700,720p' './src/routes/song.$songId.tsx' | cat -n

Repository: EtanHey/songscript

Length of output: 1316


🏁 Script executed:

# Verify if Radix UI ToggleGroupItem has built-in Space/Enter handling by checking if it prevents default
grep -A20 "ToggleGroupPrimitive.Item" './src/components/ui/toggle-group.tsx' | head -25

Repository: EtanHey/songscript

Length of output: 986


Add guard to prevent bubbling from nested interactive elements and missing aria attributes.

The wrapper divs at lines 719 and 725 only call e.stopPropagation() on onClick, not on onKeyDown. When a keyboard user focuses a ToggleGroupItem or WishlistButton and presses Space/Enter, the keydown event bubbles to this handler, which:

  1. Calls handleVideoCollapsedToggle() unintentionally
  2. Calls e.preventDefault() on a bubbled Space event — on some browsers this suppresses the click event that the nested <button> fires on keyup, potentially breaking the nested control

Additionally, the div[role="button"] is missing the aria-expanded attribute to convey its collapsed/expanded state to screen readers.

Add a target === currentTarget guard and add aria-expanded:

🛡️ Proposed fix
                <div
                  role="button"
                  tabIndex={0}
+                 aria-expanded={!isVideoCollapsed}
                  onClick={handleVideoCollapsedToggle}
                  onKeyDown={(e) => {
-                   if (e.key === 'Enter' || e.key === ' ') {
+                   if ((e.key === 'Enter' || e.key === ' ') && e.target === e.currentTarget) {
                      e.preventDefault();
                      handleVideoCollapsedToggle();
                    }
                  }}

Per coding guidelines, components with logic must have corresponding test files. Add src/routes/song.$songId.test.tsx.

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

In `@src/routes/song`.$songId.tsx around lines 705 - 710, The keyboard handler on
the role="button" wrapper is missing a guard and aria state: update the
onKeyDown handler (the arrow function that calls handleVideoCollapsedToggle) to
first check that e.target === e.currentTarget before calling e.preventDefault()
or handleVideoCollapsedToggle so events from nested interactive elements like
ToggleGroupItem or WishlistButton don't bubble and trigger the toggle; also add
aria-expanded on the role="button" div and bind it to the component's collapsed
state (the same state used by handleVideoCollapsedToggle) so screen readers know
the expanded/collapsed status; finally add a corresponding test file
(song.$songId.test.tsx) to cover keyboard interaction and nesting cases.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant