Kanzo UI
Forms

Forms

One wrapper does the wiring for every control in the library. Start here before reading any individual input page.

Every form in this library is built from one component — Field — plus whichever control you need inside it. There is no separate form system to learn, and no per-control API to memorise: a Checkbox inside a Field works exactly like an Input inside a Field.

This is the page the individual component pages assume you have read. If you have been looking at an input page wondering what the "with field" example is for, this is what it is for.

The one thing to understand

Field is not a layout wrapper. It is the piece that wires a control to its label, its description and its error message, and it does that through context — so you set state once, on the Field, and everything inside inherits it.

<Field invalid required>
  <FieldLabel>Contract title</FieldLabel>
  <Input />
  <FieldError>Give the contract a title a poster would recognise.</FieldError>
</Field>

Look at what you did not have to write:

  • No htmlFor on the label and no matching id on the input — Field generates the id and connects them. Clicking the label focuses the control.
  • No aria-invalid on the Input. invalid on the root reaches it through context.
  • No aria-describedby pointing at the error text. Field wires that too, so a screen reader announces the error when the control takes focus.
  • No conditional around FieldError. It renders only while the field is invalid, so you can leave it in the tree unconditionally.
  • No asterisk element. required on the root is what FieldRequiredIndicator reads.

What the board shows, in one line.

Give the contract a title a poster would recognise.

That list is the entire reason Field exists. Writing those five things by hand, per control, per form, is where accessible forms go to die — and skipping them is invisible until someone uses a screen reader.

Anatomy

FieldSet                      ← a titled group of related fields
├── FieldLegend               ← the group's heading
├── FieldDescription          ← help text for the whole group
└── FieldGroup                ← the layout container (and the container-query root)
    └── Field                 ← one control's worth of chrome; owns invalid/disabled/required
        ├── FieldLabel
        │   └── FieldRequiredIndicator
        ├── FieldContent      ← for checkbox/radio: title + description beside the control
        │   └── FieldTitle
        ├── <control>         ← Input, Select, Checkbox, Combobox… any of them
        ├── FieldDescription  ← persistent help text
        ├── FieldHelper       ← help text tied to the control's described-by
        ├── FieldError        ← renders only while invalid
        └── FieldSeparator

You will rarely use all of it. The common case is four lines: Field, FieldLabel, a control, FieldError.

And <control> above really is any of them. A select, a textarea, a switch and a checkbox are composed identically here — the sameness is the pattern, not an accident of the example:

Send word when it is claimed

A runner to the Amber Hall the day a party signs.

The checkbox is the one shape that differs, and only in where the words go: with a control that sits beside its label rather than under it, the text moves into FieldContent so the whole block stays one click target.

The four states you set on the root

These are the whole state API, and they all propagate to the control inside:

PropWhat it does
invalidColours the label, control and error text; reveals FieldError; sets aria-invalid
requiredReveals FieldRequiredIndicator; marks the control required
disabledDims the field and disables the control
readOnlyMarks the control read-only without dimming it
Contract ids are Q- followed by four figures.

Set them on the Field, never on the control. Setting invalid on an Input directly works, but then the label and error text do not know about it, and you are back to hand-wiring.

Where to go next

On this page