Field

The anatomy around a control: its label, a description and an error, stacked or side by side, and fieldsets that group them. The vendored shadcn parts, used as they come but for the description and the error, set small at text-xs; the wiring stays explicit, so it works the same for every control.

Playground

The end of your chat link: withokami.com/c/barber-kamzik.

Field
State

Label and control

The label names the control: point htmlFor at the control's id, and a click on the label focuses it.

Optional

Mark the fields that can stay empty, not the ones that can't: give FieldLabel or FieldLegend optional, the word in the page's language, and it follows the name in muted brackets at the label's own gap — the same in every form.

Description

Say what the value is for, or its format, under the control. Give it an id and list it in the control's aria-describedby so it's read with the label.

The end of your chat link: withokami.com/c/barber-kamzik.

Errors

FieldError says what's wrong and how to fix it, and is announced as it appears. Mark the control aria-invalid and point its aria-describedby at the error. Pass errors — a validator's { message } list — to show each problem once; with nothing to say it renders nothing.

Side by side

orientation="horizontal" puts the control beside its label — for short values in settings. Stack the label and description in FieldContent. responsive stacks until its FieldGroup is wide enough.

How soon before a slot clients can still book it.

How far ahead the calendar opens.

Fieldsets and groups

Fields that answer one question together go in a FieldSet, captioned by a FieldLegend; FieldGroup spaces them evenly, and a grid lines short ones up in a row. When the legend names a single choice among plain fields — a group of radio cards — give it variant="label" so it sits at a label's size.

Customer
Skin fade

A bare label

Label is the same label outside a Field — for rows where the control sits in a grid, like a day of opening hours.

API reference

Field

PropTypeDefaultDescription
orientation"vertical" | "horizontal" | "responsive""vertical"Label above the control, beside it, or above until the FieldGroup is wide enough.
data-disabledboolean—Dims the label along with a disabled control. Disable the control itself too.

FieldLabel · Label

PropTypeDefaultDescription
htmlForstring—The id of the control it names. Always set it — a click on the label then focuses the control.
optionalstring—FieldLabel only: the word for optional, shown after the name in muted brackets.

FieldDescription

PropTypeDefaultDescription
idstring—Give one and list it in the control's aria-describedby, so it's read with the label.

FieldError

PropTypeDefaultDescription
childrenReactNode—The message. Wins over errors.
errorsArray<{ message?: string } | undefined>—A validator's issues, each shown once — one as text, several as a list.
idstring—For the control's aria-describedby.

FieldSet · FieldLegend · FieldGroup · FieldContent

PropTypeDefaultDescription
variant"legend" | "label""legend"FieldLegend only: a section caption, or the size of a FieldLabel.
optionalstring—FieldLegend only: the word for optional, after the caption as FieldLabel shows it.

Every part passes the rest through to its element: Field, FieldGroup and FieldContent to a <div>, FieldLabel and Label to a <label>, FieldDescription to a <p>, FieldSet and FieldLegend to <fieldset> and <legend>.

Guidelines

  • Every control gets a visible label above or beside it; placeholders show an example, never the name. Labels are nouns in sentence case, with the unit when there is one: “Duration (min)”, “Price (€)”.
  • Wire it by hand, every time: htmlFor to the control's id; description and error ids into its aria-describedby. Ids must be unique on the page — prefix them by form (bk-, st-).
  • Errors say what to do, not just what failed: “Use letters, digits and dashes — no spaces or accents”, not “Invalid slug”. Show them after the person leaves the field or submits, never while they're still typing.
  • Keep descriptions to one line where you can. If every field needs one, the labels aren't clear enough.
  • Stack fields by default; go side by side only for short values in a settings list, where the label carries the explanation.