aria-labelledby pitfall #2801
-
🐛 Bug reportaria-labelledby attributes are always written to the DOM regardless of the presence of the target element. This is due to the underlying machine always falling back to the automatically generated id, regardless of whether the element exists. 💥 Steps to reproduce
💻 Link to reproductionhttps://stackblitz.com/edit/bid8xeza?file=src%2FApp.tsx&showSidebar=0 Note that the reproduction demonstrates the issue via Ark UI's Checkbox, but the bug is present in the underlying machine. 🧐 Expected behaviorThe aria-labelledby attribute should only be present if the reference element exists in the DOM. 🧭 Possible SolutionManaging ID state explicitly? Not sure. I've created a 🌍 System information
📝 Additional informationIt's also worth noting that this breaks accessibility as well: <Checkbox.Root>
<Checkbox.Label id="hello">Checkbox</Checkbox.Label>
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.HiddenInput />
</Checkbox.Root>While there is an To solve this, the id retrieval could do a check for an existing ID, i.e.: export const getLabelId = (ctx: Scope) => {
const labelId = getLabelId(ctx)
if (ctx.getById(labelId)) {
return labelId
}
const labelEl = getRootEl(ctx)?.querySelector(`[data-part="label"]`)
return labelEl?.id || undefined
}Just spitballing here. Is this worth exploring? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
|
I’m not convinced this is worth pursuing due to the added complexity. It also poses an SSR mismatch issue since we need to query the DOM. We design the state machine in a way that most parts referenced in aria-* are required. Better to visually hide them instead of omitting them all together. |
Beta Was this translation helpful? Give feedback.
-
|
That's understandable. I agree that it'd be a lot more to manage. I've actually worked out a best-of-both-worlds system that involves an element id registry + "closed" component abstraction, but that'd be a major breaking change for consumers and still doesn't account for the composite approach. That said, this is still a pitfall: <Checkbox.Root>
<Checkbox.Label id="hello">Checkbox</Checkbox.Label>
<Checkbox.Control>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.HiddenInput />
</Checkbox.Root>It might be worth enhancing the documentation to explain this. This breaks accessibility today. |
Beta Was this translation helpful? Give feedback.
Good point. I've updated the docs according to mention this pitfall