Field

Combine labels, controls, and help text to compose accessible form fields and grouped inputs.

Usage

Field provides the shell and the accessibility plumbing: FieldLabel, Input, and FieldDescription connect automatically (htmlFor / id / aria-describedby), so there are no manual ids.

Adornments are documented on InputGroup. For every combination and state side by side, see the Field Matrix.

import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSet,
} from '@epilot/spark-ui/field'
import { Input } from '@epilot/spark-ui/input'
<Field>
  <FieldLabel>Device name</FieldLabel>
  <Input placeholder="Garage wallbox" />
  <FieldDescription>Shown on the dashboard.</FieldDescription>
</Field>

Examples

With a hint

We'll never share your email.

Invalid with error message

FieldError renders a role="alert" message and dedupes an errors array (e.g. straight from react-hook-form); it renders nothing when there are no errors.

Read-only value

FieldValue shows a label–value pair in the field shell (replaces the old DisplayField). Pass readOnly on Field so the row is named via aria-labelledby instead of an invalid <label htmlFor> on the value div. Long values wrap by default; pass truncate for a single-line ellipsis. String values then get an automatic title so the full value is reachable on hover.

Serial number
WB-2041-8845

Loading skeleton

FieldSkeleton composes label and value placeholders inside the Field shell. Match ECP's gray-soft read-only look with inputColor="gray" and inputStyle="soft". Pass aria-label on Field so the loading row has an accessible name.

With an action: lay the Field out as a row. FieldContent stacks the label and value, the action sits beside it (label it in the app's language).

Account email
you@email.com

Field group

FieldGroup stacks related fields with consistent spacing.

Field set

FieldSet + FieldLegend group fields under a semantic <fieldset> / <legend>.

Address

Complete form

Charging schedule
Shown on the dashboard.

API Reference

Field

All native <div> props, plus:

PropTypeDefault
labelPosition"outset" | "inset"follows the data-label-position knob (inset)
inputStyle"soft" | "surface" | "outlined" | "ghost"follows the data-input-style knob (soft)
inputColor"brand" | "gray"follows the data-input-color knob (brand)
readOnlybooleanfalse — set for FieldLabel + FieldValue rows

Each prop follows its theming knob, resolved through the <Theme> provider (<Theme labelPosition inputStyle inputColor>); the prop overrides per instance. Raw data-* attributes without the provider don't style Field/Input.

The box carrier follows the position: inset → the Field shell (its input keeps the classic accent underline), otherwise the Input or InputGroup.

Old API mapping: variant="underline" → default (inset + soft), variant="outline"outset + outlined. The floating-label look (variant="floating" / labelPosition="overlap") was removed; use inset.

State attributes (pair with the matching prop on the input):

AttributePair withEffect
data-invalidaria-invalid on the inputlabel + text turn error red; filled inset shells tint error-soft
data-disableddisabled on the inputcontrol text dims, filled inset shells tint gray-soft; the label keeps full contrast

Subcomponents

ComponentRendersNotes
FieldLabel<label> or <div>htmlFor wired to the input; renders a <div id={labelId}> when readOnly
FieldDescription<div>linked to the input via aria-describedby automatically
FieldError<div role="alert">errors?: Array<{ message?: string }>, deduped; hidden if empty
FieldValue<div>read-only value; wraps by default, truncate for ellipsis + auto title
FieldSkeleton<div aria-hidden>loading placeholder for read-only rows; label + value bars via Skeleton
FieldSet<fieldset>groups related fields
FieldLegend<legend>variant: "legend" (default) | "label"
FieldGroup<div>vertical stack of fields
FieldContent<div>flex column for label + description next to a control
FieldTitle<div>label-styled title for non-label contexts
FieldSeparator<div> + Separatoroptional inline content (e.g. "or")

On this page