Field
Lays out a form control with its label, description and error message, and groups fields into sections.
@zenoxai/ui/components/field MarkdownShown in reports and alert emails.
<Field className="max-w-sm">
<FieldLabel htmlFor="tenant-name">Tenant name</FieldLabel>
<Input id="tenant-name" placeholder="Acme Corporation" />
<FieldDescription>Shown in reports and alert emails.</FieldDescription>
</Field>Usage
import {
Field,
FieldDescription,
FieldLabel,
} from "@zenoxai/ui/components/field"
import { Input } from "@zenoxai/ui/components/input"
<Field>
<FieldLabel htmlFor="tenant-name">Tenant name</FieldLabel>
<Input id="tenant-name" />
<FieldDescription>Shown in reports and alert emails.</FieldDescription>
</Field>
Wrap every form control in a Field: Input,
Select, Checkbox,
Radio Group, Slider and the rest. The field
sets the spacing between label, control and help text. With data-disabled it dims the
label; with data-invalid it colors the label and help text red.
Field is layout only. It does not create ids or validate: give the control an id, point
FieldLabel at it with htmlFor, and decide in your form code when a value is invalid.
| Part | What it is |
|---|---|
Field |
One control with its label and help text. |
FieldLabel |
The <label> of the control. |
FieldDescription |
Help text under the control, or under a legend. |
FieldError |
The error message. Renders nothing when it has no content. |
FieldContent |
Stacks a label and a description next to a checkbox or radio. |
FieldTitle |
A label-styled heading for a field that is not a single control. |
FieldSet |
A <fieldset> that groups related fields. |
FieldLegend |
The title of a FieldSet. variant="label" makes it label-sized. |
FieldGroup |
Stacks fields with the standard gap. Needs a definite width. |
FieldSeparator |
A divider between groups of fields, with optional text. |
Examples
Invalid
Set data-invalid on the Field and aria-invalid on the control. The label turns red, the
control gets the error ring, and FieldError shows the message. Write the message as the fix
(“Enter a domain without the protocol”), not as the fault.
<Field data-invalid className="max-w-sm">
<FieldLabel htmlFor="monitored-domain">Monitored domain</FieldLabel>
<Input
id="monitored-domain"
defaultValue="https://acme"
aria-invalid
aria-describedby="monitored-domain-error"
/>
<FieldError id="monitored-domain-error">
Enter a domain without the protocol, such as acme.com.
</FieldError>
</Field>FieldError also takes errors, an array of { message } objects in the shape form libraries
return. It removes duplicates and renders a list when more than one message remains.
Horizontal
orientation="horizontal" puts the control and the label on one line, for checkboxes, radios
and switches. Put the label and description in a FieldContent so they stack.
orientation="responsive" is vertical by default and turns horizontal when the surrounding
FieldGroup is at least 28rem wide (a container query, not the viewport).
Scan every subdomain found in DNS and certificate logs.
<Field orientation="horizontal" className="max-w-sm">
<Checkbox id="include-subdomains" defaultChecked />
<FieldContent>
<FieldLabel htmlFor="include-subdomains">Include subdomains</FieldLabel>
<FieldDescription>
Scan every subdomain found in DNS and certificate logs.
</FieldDescription>
</FieldContent>
</Field>A full form
FieldSet and FieldLegend name a group of fields; FieldGroup spaces them; FieldSeparator
divides them. A horizontal Field also lays out the form buttons.
FieldGroup is a size container (the container that orientation="responsive" queries), so it
takes no width from its content. Give it a definite width: the width of the form column, or a
class such as w-full, w-56 or max-w-sm. With w-fit it collapses to zero and every label
wraps one word per line. For the same reason, a Field placed in a row next to other fields
(a flex row of radios, for example) needs className="w-auto": by default it is w-full
and shares the row instead of fitting its label.
function FieldForm() {
const channels = [
{ label: "Email", value: "email" },
{ label: "Slack", value: "slack" },
{ label: "Webhook", value: "webhook" },
]
return (
<form
className="w-full max-w-md"
onSubmit={(event) => event.preventDefault()}
>
<FieldSet>
<FieldLegend>Notification rule</FieldLegend>
<FieldDescription>
Sends a message when an alert matches the rule.
</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="rule-name">Name</FieldLabel>
<Input id="rule-name" placeholder="Critical alerts to on-call" />
</Field>
<Field>
<FieldLabel htmlFor="rule-channel">Channel</FieldLabel>
<Select items={channels} defaultValue="email">
<SelectTrigger id="rule-channel" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{channels.map((channel) => (
<SelectItem key={channel.value} value={channel.value}>
{channel.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<Checkbox id="rule-digest" />
<FieldContent>
<FieldLabel htmlFor="rule-digest">Send a daily digest</FieldLabel>
<FieldDescription>
One message at 09:00 instead of one per alert.
</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<Button type="submit">Save rule</Button>
<Button type="button" variant="outline">
Cancel
</Button>
</Field>
</FieldGroup>
</FieldSet>
</form>
)
}For selectable cards, wrap a horizontal Field in a FieldLabel. See the choice cards in
Radio Group.
Accessibility
FieldLabelis a native<label>. It names the control only throughhtmlForpointing at the control’sid, or by wrapping the control.FieldDescriptionandFieldErrorare not linked to the control automatically. Give them anidand list it in the control’saria-describedbyso screen readers read them with the control.FieldErrorhasrole="alert", so it is announced as soon as it appears. Render it only after the user submits or leaves the field, not on every keystroke.data-invalidon theFieldonly changes styles.aria-invalidon the control is what assistive technology reads.- Every
Fieldrendersrole="group". UseFieldSetandFieldLegendwhen a group of fields needs a name.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
Field
Field lays out one form control with its FieldLabel, FieldDescription and FieldError, stacked or on one row. FieldSet with a FieldLegend groups related fields, and FieldGroup stacks fields with the standard spacing.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "responsive" | null`vertical` (default) puts the label above the control; `horizontal` puts them on one row, as for a checkbox or a switch; `responsive` is vertical until its FieldGroup is 28rem (448px) wide, then horizontal. | vertical |
FieldLabel
FieldLabel is the label of a field. Wrapped around a whole Field, it makes a selectable card: a bordered option that highlights when the checkbox or radio inside it is checked. Only an unchecked card takes the hover fill, so hover never hides the checked tint.
No props of its own. Accepts the props of the element it renders.
FieldDescription
No props of its own. Accepts the props of the element it renders.
FieldError
FieldError shows the validation messages of a field with role="alert", and renders nothing when there are none. Pass the message as children, or the `errors` of a form library.
| Prop | Type | Default |
|---|---|---|
errors | ({ message?: string; })[] | undefinedError objects, such as react-hook-form's. Duplicate messages are dropped; one message renders as text, several as a list. Children win over it. | - |
FieldGroup
FieldGroup stacks fields with the standard gap. It is also the container whose width `orientation="responsive"` responds to, so it takes no width from its content: give it a definite width (`w-full`, `w-56`, the form column), never `w-fit`, or its labels wrap one word per line.
No props of its own. Accepts the props of the element it renders.
FieldLegend
FieldLegend is the caption of a FieldSet.
| Prop | Type | Default |
|---|---|---|
variant | "label" | "legend"`legend` (default) is a section heading at text-base; `label` has the size of a FieldLabel, for a set that reads as one question, such as a radio group. | legend |
FieldSeparator
FieldSeparator is a horizontal rule between fields or groups, with optional centered text such as "Or".
No props of its own. Accepts the props of the element it renders.
FieldSet
FieldSet groups related fields under a FieldLegend, as a <fieldset>.
No props of its own. Accepts the props of the element it renders.
FieldContent
FieldContent stacks a label and a description beside the control of a horizontal Field, such as the text next to a checkbox.
No props of its own. Accepts the props of the element it renders.
FieldTitle
FieldTitle is text styled as a label that is not a <label>, such as the title of a FieldLabel card whose control is labeled otherwise.
No props of its own. Accepts the props of the element it renders.