The labeled-field scaffold — label, control, and hint/error — wired for accessibility.

Last updated

We'll never share it.

Show code
<Field id="email" label="Email" hint="We'll never share it.">
<input id="email" type="email" class="w-full rounded-md border border-neutral bg-page px-3 py-2 text-sm" />
</Field>

Add

npx @ava-ui/apogee add field

No Apogee dependencies. Input, Textarea, and Select render Field internally with a matching id — use Field standalone only for a custom control.

Usage

---
import Field from '@/components/apogee/Field/Field.astro'
---
<Field id="name" label="Name" required>
  <input id="name" class="w-full rounded-md border border-neutral bg-page px-3 py-2 text-sm" />
</Field>

Anatomy

  • Label — rendered when label is passed, for={id}; appends a aria-hidden * when required is true.
  • Control — the default slot. Pass your own input/textarea/select with a matching id, and set its aria-describedby to ${id}-hint or ${id}-error to wire the association yourself.
  • Hint — rendered at ${id}-hint when hint is set and there’s no error.
  • Error — rendered at ${id}-error with role="alert" when error is set (takes priority over hint).

API

@composable — exposed to Content Editors in AVA. Any other attribute passes through to the root <div>.

Prop Type Default Description
id string — Required. Matches the control’s id; used to derive ${id}-hint / ${id}-error.
label string — Optional label text.
tooltip string — Help text behind an info icon after the label. The icon stays focusable when the control is disabled.
hint string — Optional helper text. Shown with the error when both are set.
error string — Optional error message, rendered with role="alert" after the hint.
disabled boolean false Dims the label and marks the wrapper aria-disabled; disable the control too.
size 'small' | 'medium' | 'large' | 'xlarge' 'medium' Label and hint text size, matching the control’s size. Also set as data-size on the root.
required boolean false Shows a * next to the label (visual only — set required/aria-required on the control itself).
bare boolean false Renders only the slot, no wrapper; the parent owns label, hint and error. InputGroup uses it.
hideRequiredIndicator boolean false Hides the * while you keep required on the control (a form where every field is required).
class string — Extra classes on the root, merged last.

States

  • Error — error shows the message under the control in critical text; Input, Select and Textarea also set aria-invalid. Hint and error render together when both are set.
  • Disabled — the label switches to the disabled colour and the root gets aria-disabled. A tooltip help icon stays focusable.
  • Required — an asterisk after the label; hideRequiredIndicator keeps the semantics without it.

Examples

With a hint:

Letters, numbers, and dashes only.

Show code
<Field id="username" label="Username" hint="Letters, numbers, and dashes only.">
<input id="username" class="w-full rounded-md border border-neutral bg-page px-3 py-2 text-sm" />
</Field>

With an error:

Show code
<Field id="pw" label="Password" required error="Must be at least 8 characters.">
<input id="pw" type="password" class="w-full rounded-md border border-critical bg-page px-3 py-2 text-sm" />
</Field>

Hint and error together (the control lists both ids in aria-describedby):

At least 8 characters.

Show code
<Field id="pw2" label="Password" hint="At least 8 characters." error="Too short.">
<input id="pw2" type="password" aria-describedby="pw2-hint pw2-error" aria-invalid="true" class="…" />
</Field>

A help tooltip after the label. The icon is a button outside the <label>, so keyboard users reach it even when the control is disabled:

Found under Settings → Developers.

Rotates every 90 days.

Show code
<Field id="api" label="API key" tooltip="Found under Settings → Developers." hint="Rotates every 90 days.">
<input id="api" disabled class="…" />
</Field>
<Field id="all" label="Email" required hideRequiredIndicator>
<input id="all" type="email" required class="…" />
</Field>

Custom control (a Field wrapping something other than a native input) — set aria-describedby yourself since Field only renders the ids, it doesn’t wire them:

<Field id="plan" label="Plan">
  <select id="plan" aria-describedby="plan-hint" class="w-full rounded-md border border-neutral bg-page px-3 py-2 text-sm">
    <option>Free</option>
    <option>Pro</option>
  </select>
</Field>

Sizes and disabled

Hint text scales with small.

Hint text scales with medium.

Hint text scales with large.

Hint text scales with xlarge.

The control is disabled too.

Show code
<Field id="f-s" label="Small" size="small"><input id="f-s" class="…" /></Field>
… medium, large, xlarge …
<Field id="f-d" label="Disabled" disabled><input id="f-d" disabled class="…" /></Field>

Accessibility

  • label is a real <label for={id}> — clicking it focuses the control.
  • The error message uses role="alert" so screen readers announce it when it appears.
  • Field renders the hint/error ids but doesn’t set aria-describedby on your control — since the control is an opaque slot, wire that attribute yourself (Input/Textarea/Select do this for you).
  • The * for required is aria-hidden (decorative) — set the real required/aria-required attribute on the control. hideRequiredIndicator removes only the glyph.
  • tooltip renders a button with aria-label="More information" beside the label, outside it, so it stays in the tab order when the control is disabled; Tooltip describes the button, not the control.

Tokens

Token Used for
--foreground-neutral Label text
--foreground-neutral-faded Hint text
--foreground-critical Required * and error text

Do / Don’t

  • Do keep the id on Field and your control in sync — it’s how hint/error ids line up.
  • Do use error instead of hint once a field is invalid; don’t show both.
  • Don’t nest more than one control per Field — one label should describe one input.
  • Don’t rely on the visual * alone to mark a field required — set the attribute on the control too.

Content

  • Label is a short noun phrase (1–3 words), sentence case, no ending colon or period.
  • Hint is one short sentence explaining format or constraints, not a duplicate of the label.
  • Error names the specific problem and, where possible, the fix (“Must be at least 8 characters”), one sentence.