Skip to content

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

@loicplaire

Description

@loicplaire

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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions