Skip to content

docs: consolidate Field.Label callout to field docs only#3826

Open
Copilot wants to merge 6 commits intomainfrom
copilot/fix-aria-labelledby-issue
Open

docs: consolidate Field.Label callout to field docs only#3826
Copilot wants to merge 6 commits intomainfrom
copilot/fix-aria-labelledby-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 19, 2026

The Field.Label / aria-labelledby accessibility guidance was duplicated across field.mdx, checkbox.mdx, and switch.mdx, and was originally scoped under the Checkbox section in the Field docs despite applying to all form controls.

  • Moved the callout to the general Examples section in field.mdx so it applies to all inputs
  • Removed duplicate callouts from checkbox.mdx and switch.mdx — the Field doc is the single source of truth for Field-level behavior
  • Updated wording from "hidden input's" → "form control's" since it's no longer checkbox-specific
Original prompt

This section details on the original issue you should resolve

<issue_title>Checkbox and Switch machines set aria-labelledby to non-existent label when used inside Field</issue_title>
<issue_description># Bug report

Checkbox and Switch machines unconditionally set aria-labelledby on the hidden input via getHiddenInputProps(), even when no label element exists in the DOM. This creates a broken ARIA reference that points to a non-existent element.

Steps to reproduce

  1. Render a Checkbox (or Switch) inside a Field without a Field.Label
  2. Provide an accessible name via aria-label instead of a visible label
  3. Inspect the hidden input element
  4. The input has aria-labelledby="field::v-0::label" pointing to an element that doesn't exist

Link to reproduction

Stackblitz reproduction: https://stackblitz.com/edit/lpxfxp4j?file=src%2FApp.vue&showSidebar=0

Expected behaviour

When no label element is rendered, getHiddenInputProps() should not set aria-labelledby. The attribute should only be present when a corresponding label element actually exists in the DOM.

When aria-label is the only labelling mechanism, the hidden input should have aria-label without a conflicting aria-labelledby pointing to nothing.

Possible Solution

In checkbox.connect.ts and switch.connect.ts, getHiddenInputProps() unconditionally sets:

"aria-labelledby": getLabelId(scope),

getLabelId always returns a string (it falls back to a generated ID), so aria-labelledby is always set.

One fix would be to only include aria-labelledby when a label element is present in the DOM:

"aria-labelledby": scope.getById(getLabelId(scope)) ? getLabelId(scope) : undefined,

Or alternatively, skip it when the consumer has provided aria-label directly.

The same pattern exists in both @zag-js/checkbox and @zag-js/switch. Radio is not affected because RadioGroup.Item manages labelling independently.

System information

Software Version(s)
Zag Version 1.31.1
Browser All
Operating System All

Your Company/Team

Deputy / Design System team

Additional information

The issue is amplified by the Ark UI Field integration. In @ark-ui/vue, useCheckbox overrides the machine's ids.label with field.ids.label:

ids: {
  label: field?.value.ids.label,
  hiddenInput: field?.value.ids.control
},

So even if the checkbox's own Checkbox.Label is rendered, the aria-labelledby points to the Field's label ID instead of the checkbox label's ID. When no Field.Label is rendered, this creates the broken reference.
</issue_description>

<agent_instructions>Just document that Field.Label is required (even if visually hidden), and that aria-label alone will not work for controls inside Field in context of accessible labelling</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@anubra266 @loicplaire Totally understand not wanting visible text there, but you don't have to choose between the two.

If you add Field.Label with an sr-only/visually-hidden class it stays completely invisible in the UI, but it creates the DOM element that the hidden input's aria-labelledby is already pointing to. Without it, that reference is dangling and the checkbox has no accessible name at all.

The practical impact: Voice Control users (Dragon, iOS Voice Control) say "click Accept terms" to activate fields; they can't do that if the input has no name. Screen readers will also either skip it or read something unhelpful.

So the fix is just:

<Field.Label class="sr-only">Accept terms</Field.Label>

Nothing changes visually, but it's no longer a WCAG 4.1.2 failure.

TL:DR The Field label is an accessibility requirement.</comment_new>
<comment_new>@segunadebayo
@anubra266 is right. You need to make sure you’re always rendering a label. You can hide it visually but it’s important for accessibility.

It’s not worth adding extra client side checks in Zag for this. Even if we did, it won’t work in SSR environments.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 19, 2026

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

Project Deployment Actions Updated (UTC)
ark-docs Ready Ready Preview Mar 20, 2026 8:59pm

Request Review

…with Checkbox and Switch

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix aria-labelledby reference in Checkbox and Switch components docs: document that Field.Label is required for accessible labelling with Checkbox and Switch Mar 19, 2026
Copilot AI requested a review from anubra266 March 19, 2026 20:10
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title docs: document that Field.Label is required for accessible labelling with Checkbox and Switch docs: document Field.Label requirement for Checkbox/Switch inside Field Mar 19, 2026
…rding

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title docs: document Field.Label requirement for Checkbox/Switch inside Field docs: fix Field label guidance for Checkbox/Switch Mar 19, 2026
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title docs: fix Field label guidance for Checkbox/Switch docs: replace sr-only/visually-hidden with framework-agnostic wording Mar 19, 2026
Copilot AI changed the title docs: replace sr-only/visually-hidden with framework-agnostic wording docs: consolidate Field.Label callout to field docs only Mar 20, 2026
@anubra266 anubra266 requested a review from segunadebayo April 1, 2026 10:17
@segunadebayo segunadebayo marked this pull request as ready for review April 2, 2026 11:14
@segunadebayo
Copy link
Copy Markdown
Member

This can be misleading since the user can also render the <Component>.Label and set the ids={{ label: '<x>' }}

We need to adjust the docs to mostly emphasize that its important to always label form elements you either

  • render Field.Label, or
  • render the <Component>.Label and set the ids={{ label: '<x>' }}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checkbox and Switch machines set aria-labelledby to non-existent label when used inside Field

3 participants