apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Field
Variant↳ other

Forms

Field

Form-field composition wrapper that pairs a label, optional description, a single form control, and helper / error text. Four label positions (top / start / floating / hidden), required / optional indicators, fieldset / legend semantics for grouped controls, label addons, and start/end adornments. Integrates with every form control via FieldContext — zero source-code changes to any existing control.

Overview

<Field /> is the canonical composition wrapper that pairs a label, an optional description, a single form control, and an optional helper text or error message.

Every form control (<Input>, <Textarea>, <Select>, <Combobox>, <Checkbox>, <Switch>, <Radio>, <NumberInput>, <Rating>, <TagsInput>) re-uses useFormFieldA11y under the hood, so wrapping any of them in <Field> flows id, name, required, invalid, disabled, readOnly and the composed aria-describedby automatically — with zero source-code changes to the control.

It ships in two flavors that resolve to the same DOM:

  • Prop-driven — pass label / description / helperText / error props. Best for the 90% case.
  • Compound — <Field.Label> / <Field.Description> / <Field.Helper> / <Field.Error> / <Field.Control> children. Best when you need custom ordering or to mix multiple subparts.

Both APIs honor the same context, so subparts can be mixed freely with the prop-driven defaults.

Overview — label, helper text, and validation states

Loading preview…
Overview.tsx

Anatomy

SubpartElementRole
<Field><div data-field-root> or <fieldset data-field-root>container + FieldContext provider
<Field.Label><label htmlFor={controlId}>the visible label; auto-sr-only when labelPosition='hidden'
<Field.Description><p id={descriptionId}>long-form guidance above the control; wired to aria-describedby
<Field.Helper><p id={helperId}>short hint below the control; suppressed when error is set
<Field.Error><p id={errorId} role="alert">error message; sets aria-invalid="true" on the control
<Field.Control><div data-field-control>explicit control-row slot; useful with start / end adornments

Examples

Basic

Loading preview…
Basic.tsx

WithError

Loading preview…
WithError.tsx

Required

Loading preview…
Required.tsx

Optional

Loading preview…
Optional.tsx

LabelPositionStart

Loading preview…
LabelPositionStart.tsx

LabelPositionFloating

Loading preview…
LabelPositionFloating.tsx

LabelHidden

Loading preview…
LabelHidden.tsx

WithDescription

Loading preview…
WithDescription.tsx

WithHelper

Loading preview…
WithHelper.tsx

WithLabelIcon

Loading preview…
WithLabelIcon.tsx

WithLabelAddon

Loading preview…
WithLabelAddon.tsx

WithStartEndAdornment

Loading preview…
WithStartEndAdornment.tsx

Fieldset

Loading preview…
Fieldset.tsx

Compound

Loading preview…
Compound.tsx

Disabled

Loading preview…
Disabled.tsx

ReadOnly

Loading preview…
ReadOnly.tsx

Sizes

Loading preview…
Sizes.tsx

EveryControl

Loading preview…
EveryControl.tsx

InForm

Loading preview…
InForm.tsx

Props

PropTypeDefaultDescription
asenum—Container element. `'fieldset'` auto-routes the label into a `<legend>`. Default `'div'`.
childrenReactNode—Field children — typically the form control, optionally interleaved with `<Field.*>` subparts.
classNamestring——
descriptionReactNode—Long-form guidance rendered between the label and the control. Maps to `<Field.Description>` and is wired into the inner control's `aria-describedby`.
disabledboolean—Propagates to the inner control via context.
endAdornmentReactNode—Visual icon / text AFTER the control row (inside the field layout).
errorReactNode—Error message rendered below the control (replaces `helperText` when present). Sets `aria-invalid="true"` and wires into the inner control's `aria-describedby`.
helperTextReactNode—Short hint rendered below the control. Maps to `<Field.Helper>`. Wired into the inner control's `aria-describedby`. Hidden when `error` is non-falsy.
hideRequiredIndicatorboolean—Hide the `*` indicator even when `required={true}` (still sets `aria-required`).
htmlForstring—`id` for the inner control. When omitted, Field generates a stable id via `useId` and the inner control reads it from context (replacing its own auto-id).
labelReactNode—Visible label. Rendered into `<Field.Label>` (or `<legend>` when `as="fieldset"`).
labelAddonReactNode—Trailing addon rendered after the label text (e.g. a `<Badge>` or info `<Tooltip>`).
labelPositionenum—Position of the label relative to the control. Default `'top'`.
labelWidthstring—CSS length applied to the label column when `labelPosition='start'`. Ignored otherwise. Accepts any CSS length token (`'120px'`, `'8rem'`, `'30%'`, …).
namestring—Propagated to the inner control via context.
optionalboolean—Marks the field as explicitly optional. Renders a muted "(optional)" hint after the label. Mutually exclusive with `required` (Field warns in dev when both are set).
readOnlyboolean—Propagates to the inner control via context.
refRef<HTMLElement>—Forwarded to the underlying `<div>` / `<fieldset>` root. Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see {@link https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom React Docs}
requiredboolean—Marks the field as required. Renders a red `*` indicator after the label (aria-hidden) and sets `aria-required="true"` on the inner control via context.
sizeenum—Visual size — propagates to the inner control. Default `'md'`.
startAdornmentReactNode—Visual icon / text BEFORE the control row (inside the field layout).
styleCSSProperties——
sxSx——

Label positions

<Field> supports four label positions, each rendered with the same DOM shape (<label> → <control> → optional helper / error) but laid out differently:

ValueLayoutWhen to use
'top' (default)Label above the control, stacked columnThe default for almost every form
'start'Label on the leading inline edge with a labelWidth gutterSettings pages, admin forms with many short fields
'floating'Label inside the control border; collapses upward on focus / value (pure CSS)Sign-in pages, marketing CTAs, sites with hero forms
'hidden'Label is sr-only (visually hidden, still associated with the control)Search fields, icon-only inputs where context is obvious

The 'floating' layout is implemented with :placeholder-shown and :focus sibling selectors; the inner control must render a placeholder (use placeholder=" " if you want an empty collapsed state). It works with <Input>, <Textarea>, <NumberInput>, <Combobox>. For controls without a placeholder (Checkbox, Switch, Radio) the label falls back to top-positioned.

Required vs Optional

The required and optional props are visual + a11y opposites:

PropVisualA11y
requiredRed * after the labelaria-required="true" on the inner control; * itself is aria-hidden
optionalMuted (optional) textNo aria-required; nothing announced separately

They are mutually exclusive — if both are passed, required wins (and a dev warning fires). Use hideRequiredIndicator={true} to keep aria-required but hide the visual * (e.g. when every field in a form is required and the asterisks would be visual noise).

Fieldset semantics

When <Field> wraps multiple controls (a checkbox group, a radio group, multiple toggles), pass as="fieldset" so the wrapper renders as <fieldset> with the label routed into <legend>. AT users hear the legend before each grouped control, which is the W3C-recommended pattern for grouped form controls.

tsx
<Field as="fieldset" label="Notifications">
  <Field.Description>Pick at least one channel.</Field.Description>
  <Stack gap={2}>
    <Checkbox name="email">Email</Checkbox>
    <Checkbox name="sms">SMS</Checkbox>
    <Checkbox name="push">Push</Checkbox>
  </Stack>
</Field>
<Field as="fieldset" label="Notifications">
  <Field.Description>Pick at least one channel.</Field.Description>
  <Stack gap={2}>
    <Checkbox name="email">Email</Checkbox>
    <Checkbox name="sms">SMS</Checkbox>
    <Checkbox name="push">Push</Checkbox>
  </Stack>
</Field>

Compound API

Use the compound API when you need to control ordering precisely, or to mix <Field.Helper> and <Field.Error> with custom inline content:

tsx
<Field required>
  <Field.Label>Email address</Field.Label>
  <Field.Description>Used for billing notifications and password resets.</Field.Description>
  <Input type="email" name="email" />
  <Field.Helper>Lowercase letters only.</Field.Helper>
</Field>
<Field required>
  <Field.Label>Email address</Field.Label>
  <Field.Description>Used for billing notifications and password resets.</Field.Description>
  <Input type="email" name="email" />
  <Field.Helper>Lowercase letters only.</Field.Helper>
</Field>

Field auto-detects which subparts the consumer rendered and suppresses its prop-driven counterparts so the same prop never renders twice.

Adornments

startAdornment and endAdornment render inside the field layout, on either side of the control row. Use them for inline visual cues that should participate in the control row but stay outside the control's own border (e.g. a currency symbol, an external URL prefix):

tsx
<Field
  label="Amount"
  startAdornment={<span>$</span>}
  endAdornment={<span className="text-fg-muted">USD</span>}
>
  <Input type="number" />
</Field>
<Field
  label="Amount"
  startAdornment={<span>$</span>}
  endAdornment={<span className="text-fg-muted">USD</span>}
>
  <Input type="number" />
</Field>

Adornments are aria-hidden by default — they're visual aids, not labels. If the adornment carries meaning (e.g. a button), set the control's aria-label accordingly.

Accessibility

  • Label association: <label htmlFor={controlId}> is wired through context. The inner control reads id from FieldContext (replacing its own auto-generated id).
  • aria-describedby: composed automatically from description id → helper id → error id (in that order). Description is read first, then either helper or error (never both — error replaces helper).
  • aria-invalid: set on the inner control when error is non-falsy.
  • aria-required: set on the inner control when required is true. The visible * glyph is aria-hidden to avoid double-announcement.
  • Fieldset / Legend: as="fieldset" renders proper <fieldset><legend> semantics — no manual aria-labelledby wiring required.
  • Floating label: when collapsed (control has value or is focused), the label remains associated via htmlFor and stays in the accessibility tree.
  • role="alert" on <Field.Error> so error messages are announced when they appear mid-flow (e.g. on blur-triggered validation).
  • axe-core: 0 violations across all label positions, required / optional / error / fieldset / floating modes, and the full 10-control integration matrix.

RTL

All layouts use logical axes (flex-row + flex-col), logical insets (start / end), and direction-agnostic gap spacing. The required * indicator and (optional) text appear after the label text in both LTR and RTL since they follow document direction. labelWidth applies to the leading inline edge regardless of direction.

i18n

When wrapped in <I18nProvider>, Field will source these strings from the active locale bundle. Until then, the defaults below are hardcoded English:

KeyDefault (en)Notes
field.required"*" (visible)The visible glyph; aria-required carries the AT announcement
field.optional"(optional)"Visible label addon when optional={true}

For now, override per field via the labelAddon prop if you need a different visible string.

Theming

fieldRecipes is a 9-slot recipe (root, labelColumn, label, controlRow, description, helper, error, requiredIndicator, optionalIndicator, adornment). Override per slot via theme.components.Field.styleOverrides.{slot}:

ts
theme.components.Field = {
  styleOverrides: {
    label: { fontWeight: 600 },
    error: { fontStyle: 'italic' },
  },
};
theme.components.Field = {
  styleOverrides: {
    label: { fontWeight: 600 },
    error: { fontStyle: 'italic' },
  },
};

Integration with form controls

Field integrates with every form control that uses useFormFieldA11y:

ControlIntegrationNotes
Input✅id, required, invalid, describedBy via context
Textarea✅Same
Select✅id flows to the trigger
Combobox✅id flows to the text input; listbox stays associated
Checkbox✅id, required, invalid via context
Switch✅id, required, invalid via context
Radio✅Use as="fieldset" for radio groups
NumberInput✅id, required, invalid via context
Rating✅id, required, invalid via context
TagsInput✅id, required, invalid via context
Slider⚠️ Wrap-onlyDoesn't use useFormFieldA11y (each thumb has its own role="slider"). Label still associates via <label for>.

For controls outside the design system (e.g. third-party datepicker / colorpicker), wrap them in Field and pass id={field.controlId} manually, or read FieldContext directly with useFieldContext().

Do / Don't

✅ Do use <Field> for every visible form control — even one-off inputs benefit from the consistent label / helper / error chrome.

✅ Do prefer error over helperText when validation fails; Field will swap them automatically and set aria-invalid for you.

✅ Do use as="fieldset" for grouped controls (checkbox / radio groups, multi-toggle rows).

❌ Don't double-set the same a11y prop on both the inner control and Field — Field wins, and a dev warning will flag the duplication.

❌ Don't use labelPosition="floating" for controls without a placeholder (Checkbox, Switch, Radio). Field warns in dev when this is detected.

❌ Don't wrap multiple input controls (e.g. two <Input>s side by side) in a single non-fieldset <Field> — the label / describedBy / id can only point to one inner control.