Relationships
#1662 ApiKeyManager's collapsed trigger points aria-controls at an element it never renders
Opened by keeb · 8/14/2026
Problem
islands/ApiKeyManager.tsx renders aria-controls={panelId} on its collapsed trigger (:254), but the collapsed branch returns before the panel exists (:247 — if (!expanded.value) { return <section><button …/></section> }). The element aria-controls names is only rendered by the expanded branch, so in the state where the attribute actually matters it is a dangling reference to an id that is not in the document.
// islands/ApiKeyManager.tsx:247-273 (collapsed)
if (!expanded.value) {
return (
<section …>
<button
aria-expanded="false"
aria-controls={panelId} // ← nothing in the DOM has this id yet
…Why it matters
aria-controls pointing at a missing id is invalid per ARIA and is what assistive technology uses to offer "jump to the controlled region". A dangling reference is silently dropped — the user gets no relationship at all — and validators flag it.
The aria-expanded="false" beside it is correct (it is the literal string, not a boolean — see the sibling issue).
Fix
Keep the panel in the DOM and hide it, rather than returning early — which is what islands/CollapsibleSection.tsx:78-85 already does (class={expanded.value ? "" : "hidden"} on an always-rendered #${id}-panel). That makes aria-controls resolve in both states, and it is the same shape #1657 gave the profile editor.
Note the panel's contents are lazily fetched on expand, so keeping the wrapper rendered does not mean fetching early — the wrapper can render empty.
Environment
swamp-club.com, islands/ApiKeyManager.tsx:247-273. Found while implementing #1657 on the adjacent owner panel.
Open
No activity in this phase yet.
Sign in to post a ripple.