Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@

**Learning:** For custom toggle buttons or selectable elements acting as a group (like language selection) that use standard HTML `<button>` tags, `aria-pressed={boolean}` should be applied to convey the active state to assistive technology. Furthermore, when decorative emojis are used alongside text labels, wrapping the emoji in a `<span>` with `aria-hidden="true"` prevents redundant or confusing screen reader announcements.
**Action:** Always apply `aria-pressed` to custom toggle buttons and add `aria-hidden="true"` to wrapper spans around decorative emojis.

## 2026-07-24 - Restore keyboard accessibility to custom buttons

**Learning:** When styling custom interactive UI elements that remove native browser focus outlines (e.g., pixel-btn components or custom toggles), keyboard accessibility is lost for tab-navigation users.
**Action:** Always restore keyboard accessibility by adding explicit focus-visible: utility classes (like focus-visible:ring-agent-cyan focus-visible:ring-2 focus-visible:outline-none) to custom buttons. This ensures keyboard users can see their focus state without applying persistent focus outlines for mouse users.
16 changes: 13 additions & 3 deletions src/components/kids/layout/background-music.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ export function MusicButton() {
return (
<button
onClick={toggleMusic}
className="pixel-btn pixel-btn-amber flex h-8 items-center px-2 py-1.5"
className="pixel-btn pixel-btn-amber focus-visible:ring-agent-cyan flex h-8 items-center px-2 py-1.5 focus-visible:ring-2 focus-visible:outline-none"
aria-label={labelText}
title={labelText}
aria-pressed={isPlaying}
>
{isPlaying ? <PixelSpeakerOn aria-hidden="true" /> : <PixelSpeakerOff aria-hidden="true" />}
</button>
Expand All @@ -197,11 +198,20 @@ export function MusicVolumeSlider() {
<div className="flex items-center justify-between">
<button
onClick={() => setIsPlaying(!isPlaying)}
className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
aria-pressed={isPlaying}
className={`focus-visible:ring-agent-cyan rounded-lg px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none ${
isPlaying ? "bg-[#22C55E] text-white" : "bg-gray-200 text-gray-600"
}`}
>
{isPlaying ? "πŸ”Š On" : "πŸ”‡ Off"}
{isPlaying ? (
<>
<span aria-hidden="true">πŸ”Š</span> On
</>
) : (
<>
<span aria-hidden="true">πŸ”‡</span> Off
</>
)}
</button>
<span className="text-sm text-[#5D4037]">{Math.round(volume * 100)}%</span>
</div>
Expand Down
Loading