Yugen UI

Labelling components

Every interactive control needs a clear accessible name. The best option is always a visible label, connected to the control with Field or Label.

Visible labels: Field and Label

A visible label tells everyone what the control is for: mouse, keyboard, screen reader and voice input users alike.

Brightness60
<Field label="Full name" required>
  <Input autoComplete="name" />
</Field>

<Checkbox label="Get the monthly newsletter" />
<Switch label="Battery saver" />
<Slider label="Brightness" showValue defaultValue={[60]} />

{/* Custom layout: Label + htmlFor pointing to the control's id */}
<Label htmlFor="bio" size="large">About you</Label>
<Textarea id="bio" rows={3} />

{/* Label with an explanation */}
<InfoLabel htmlFor="vat" info="The 10- or 13-digit tax ID issued by the tax authority.">Tax ID</InfoLabel>
<Input id="vat" inputMode="numeric" />

Checkbox, Switch, Radio and Slider have their own label prop and connect it with useId. Wrap Input / Textarea / Select / Dropdown / Combobox in Field: it generates the id and sets htmlFor, aria-describedby and aria-invalid.

Icon-only buttons

An icon has no name. Every icon-only button needs an aria-label; add a Tooltip with the same text so sighted users can read it too.

<div role="toolbar" aria-label="Text formatting" className="flex gap-1">
  <SimpleTooltip content="Bold (Ctrl+B)">
    <Button appearance="subtle" icon={<TextBold20Regular />} aria-label="Bold" aria-keyshortcuts="Control+B" />
  </SimpleTooltip>
  <SimpleTooltip content="Italic (Ctrl+I)">
    <Button appearance="subtle" icon={<TextItalic20Regular />} aria-label="Italic" aria-keyshortcuts="Control+I" />
  </SimpleTooltip>
  <SimpleTooltip content="Underline (Ctrl+U)">
    <Button appearance="subtle" icon={<TextUnderline20Regular />} aria-label="Underline" aria-keyshortcuts="Control+U" />
  </SimpleTooltip>
</div>

{/* Give the label enough context when the page has many identical buttons */}
<Button appearance="subtle" icon={<Delete20Regular />} aria-label="Delete file Q3 report.pdf" />
  • A Tooltip does not replace the label. It only shows on hover / focus, and Radix attaches it as a description (aria-describedby). The name still has to come from aria-label.
  • The label should match the tooltip text (WCAG 2.5.3, Label in Name) so voice control users can say the right name.
  • When a page has many buttons with the same icon (Delete on every row), add context to the label: “Delete file Q3 report.pdf”.

Descriptions & validation: aria-describedby

The name says what the control is; the description adds what the user needs to know. Field connects both the hint and the validationMessage to the control.

The email needs an @ and a domain, for example ann@company.com.Used to sign in and receive invoices.
<Field
  label="Work email"
  hint="Used to sign in and receive invoices."
  validationState="error"
  validationMessage="The email needs an @ and a domain, for example ann@company.com."
  required
>
  <Input type="email" defaultValue="ann.company.com" />
</Field>
<!-- HTML generated by Field -->
<label for=":r5:">Work email<span>*</span></label>
<input
  id=":r5:"
  required
  aria-invalid="true"
  aria-describedby=":r5:-message :r5:-hint"
/>
<span id=":r5:-message">The email needs an @ and a domain, for example ann@company.com.</span>
<span id=":r5:-hint">Used to sign in and receive invoices.</span>

A screen reader reads: “Work email, required, invalid entry, edit text, The email needs an @ and a domain…, Used to sign in…”. The validationMessage is not read out when it appears. When the user submits a form with errors, move focus to the first invalid field; its label and error description are then read together.

Labels for groups: RadioGroup & Checkbox groups

Each option has its own label, but the group needs a name too. Otherwise a screen reader user hears “Standard” without knowing what they are choosing.

Notify me by
{/* RadioGroup: role="radiogroup" takes its name from a visible label through aria-labelledby */}
<div className="flex flex-col gap-1">
  <Label id="shipping-label" weight="semibold">Shipping method</Label>
  <RadioGroup aria-labelledby="shipping-label" defaultValue="standard">
    <Radio value="standard" label="Standard (3–5 days)" />
    <Radio value="express" label="Express (same day)" />
  </RadioGroup>
</div>

{/* Checkbox group: fieldset + legend is the native way, no ARIA needed */}
<fieldset>
  <legend className="pb-1 text-body1 font-semibold">Notify me by</legend>
  <div className="flex flex-col">
    <Checkbox label="Email" defaultChecked />
    <Checkbox label="SMS" />
    <Checkbox label="Mobile app" />
  </div>
</fieldset>

Note: the Field's <label for> can only name real form elements (input, select, textarea, button). If you put a RadioGroup inside a Field with a render-prop, the group still needs aria-labelledby pointing to a label with an id, as in the example above.

A placeholder is not a label

A placeholder disappears once you start typing, has low contrast (foreground-4) and is not read as the name by every screen reader.

✕ Don't

One typed character removes the context; the expected format is unknown.

✓ Do
10 digits, starting with 0.

The label is always visible; the format is in the hint; the placeholder is only an example.

Do
  • Give every control a visible label: short, specific, sentence case.
  • Use aria-label for icon-only buttons, with a Tooltip showing the same text.
  • Name groups (RadioGroup, Checkbox groups) with aria-labelledby or fieldset + legend.
  • Put formats and constraints in the hint so they are connected through aria-describedby.
Don't
  • Using a placeholder instead of a label.
  • Repeating the control type in the label (“Save button”, “Name input”). Screen readers already announce the role.
  • Setting an aria-label that differs from the visible text.
  • Relying on a Tooltip as the only name.