Skip to main content
← Back to list
01Issue
BugOpenSwamp ClubPublic
AssigneesNone

Relationships

#1666 aria-expanded is a raw boolean in 12 places — collapsed state is unannounced to screen readers

Opened by keeb · 8/15/2026

Problem

aria-expanded is written as a raw boolean in 12 places across 8 islands. Preact omits an attribute whose value is false, so when those controls are collapsed the attribute is absent from the DOM entirely — assistive technology cannot distinguish "collapsed" from "not a disclosure widget at all".

The expanded state looks correct, which is why this survives manual testing: you only see the bug when the control is closed, and only through a screen reader.

Where

islands/Nav.tsx:171                       aria-expanded={accountOpen.value}
islands/Nav.tsx:247                       aria-expanded={menuOpen.value}
islands/ExtensionRegistry.tsx:231         aria-expanded={open.value}
islands/ExtensionRegistry.tsx:791         aria-expanded={drawerOpen.value}
islands/ManualCard.tsx:52                 aria-expanded={expanded.value}
islands/lab/LabShell.tsx:1432             aria-expanded={showNewForm.value}
islands/CollapsibleSection.tsx:56         aria-expanded={expanded.value}
islands/metrics/D3TimeSeriesChart.tsx:966 aria-expanded={open}
islands/InviteOperative.tsx               aria-expanded={showDropdown.value}
islands/AdminMonitor.tsx                  aria-expanded={open.value}
islands/AdminMonitor.tsx                  aria-expanded={isExpanded}   (x2)

islands/lab/MentionAutocomplete.tsx:207 has the same shape via a spread object ("aria-expanded": open.value), and islands/ApiKeyManager.tsx:253 is hardcoded aria-expanded="false" — correct type, but it never updates.

The main site navigation (Nav.tsx) is the highest-impact one: it is on every page, and both the account menu and the mobile menu are affected.

Fix

aria-expanded={open ? "true" : "false"} at each site. Mechanical, no behaviour change for sighted users.

Worth adding a static sweep at the same time so a 13th cannot appear — nothing in the type system objects, since aria-expanded accepts a boolean happily. A sweep was written while fixing one instance of this and pulled back out, because making it pass would have meant an 8-file cleanup inside an unrelated PR:

// aria-expanded={ followed by anything that is not a string or a ternary
const BAD = /aria-expanded=\{(?!\s*(?:["'`]|[^}]*\?[^}]*:))[^}]*\}/g;

Context

Found by the CI UI-review agent against components/lab/AudienceControl.tsx in #1092, where it was introduced fresh and has been fixed. The agent cited it as a documented blocking convention, which it is — these 12 predate that PR and were not in its scope.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/15/2026, 1:20:29 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.