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
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.
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).
Field group
FieldGroup stacks related fields with consistent spacing.
Field set
FieldSet + FieldLegend group fields under a semantic <fieldset> /
<legend>.
Complete form
API Reference
Field
All native <div> props, plus:
| Prop | Type | Default |
|---|---|---|
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) |
readOnly | boolean | false — 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):
| Attribute | Pair with | Effect |
|---|---|---|
data-invalid | aria-invalid on the input | label + text turn error red; filled inset shells tint error-soft |
data-disabled | disabled on the input | control text dims, filled inset shells tint gray-soft; the label keeps full contrast |
Subcomponents
| Component | Renders | Notes |
|---|---|---|
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> + Separator | optional inline content (e.g. "or") |