Kanzo UI
Forms

Field

The chrome around a control — label, description, helper and error text, wired together — and the fieldset that groups several of them under one legend.

Ark UI

One line — it is what the board shows and what a party quotes back.

Usage

import { Field, FieldDescription, FieldLabel } from "@kanzo-tech/ui";
<Field invalid>
  <FieldLabel>Contract id</FieldLabel>
  <Input />
  <FieldError>Contract ids are Q- followed by four figures.</FieldError>
</Field>

Anatomy

FieldSet
├── FieldLegend
├── FieldDescription
├── FieldGroup
│   └── Field
│       ├── FieldLabel
│       │   └── FieldRequiredIndicator
│       ├── FieldContent
│       │   └── FieldTitle
│       ├── <control>
│       ├── FieldDescription
│       ├── FieldHelper
│       ├── FieldError
│       └── FieldSeparator
├── FieldSetHelper
└── FieldSetError

Orientation

vertical stacks label above control, horizontal puts them on one line, and responsive starts vertical and becomes horizontal once the surrounding FieldGroup container is wide enough — the switch is a container query, so a field in a narrow panel stays stacked.

Invalid

invalid on the root is what colours the label, the control and FieldError at once, and FieldError only renders while the field is invalid.

Contract ids are Q- followed by four figures.

Helper tone

FieldHelper is the text wired into the control's aria-describedby, and it takes a tone: muted (the default), info or warning. Use one for a message the value survives — a warning that a P.O. box cannot be delivered to, a note about what the field will do — and keep FieldError for what blocks. Ark gives every helper the same id, so a field has one; put a second line in FieldDescription.

Couriers cannot deliver to a P.O. box.

Shown to the people you share with.

Changing it does not rename past exports.

Columns

FieldGroup stacks its fields. Give it columns and it lays them out in a grid instead: one to four equal columns, or "auto" to fit as many as the width allows. A responsive count is a class of your own — columns={1} with sm:grid-cols-2 is one column on a phone and two beyond it — and a field that should take the whole row takes col-span-full. It is a prop on a part that already existed, not a Grid component.

A Field with orientation="responsive" switches on the width of the group, not of its cell, so inside a narrow column it will still go horizontal; use vertical in a grid.

Required

Optional

FieldRequiredIndicator is the one part that marks either kind of field. On a required field it renders the asterisk; on any other it renders its fallback, so a form that wants optional fields called out passes the words once and never writes "(optional)" into a label. The asterisk is hidden from assistive technology, because the control's required already announces it; the fallback is read as part of the label, because nothing else says a field is optional. The words are yours — the library ships no copy.

Disabled

Assigned when the contract is posted, and never reused.

Field set

FieldSet renders a real fieldset/legend, so a group of related fields is announced as a group rather than as loose controls.

Posting

Which hall signs the contract, and where the work is.

FieldLegend defaults to variant="legend" — a section heading at text-base. Use variant="label" when the group is one question with several controls (a checkbox group, a radio group), so its legend reads at the same size as a FieldLabel rather than as a heading above it.

Messages for the whole group

FieldSetHelper and FieldSetError are the group's own messages, and they are not the same parts as FieldHelper / FieldError: those belong to one control and are wired into that control's aria-describedby, while these are wired into the fieldset's, so a screen reader announces them for the group rather than on whichever field happens to be focused. Like FieldError, FieldSetError renders only while its root is invalid, so it can stay in the tree unconditionally.

Muster point
Where the party forms up before it walks.A party cannot be told to meet nowhere.

One asymmetry worth knowing, because it is Ark's and not ours: disabled on a FieldSet does reach every Field inside it — a real <fieldset disabled> disables its controls, and Ark's Field reads the fieldset's disabled as its default. invalid does not cascade; it marks the set and reveals FieldSetError, but each Field keeps its own invalid. Group-level and field-level validity are separate claims, so set both if you mean both.

API Reference

Field

PropTypeDefault
orientation"vertical" | "horizontal" | "responsive""vertical"
reversebooleanfalse

Extends Field.Root from Ark, so invalid, disabled, required, readOnly and id pass through.

FieldHelper

PropTypeDefault
tone"muted" | "info" | "warning""muted"

Extends Field.HelperText from Ark.

FieldGroup

PropTypeDefault
columns1 | 2 | 3 | 4 | "auto"stacked

FieldSet

Extends Fieldset.Root from Ark, so disabled, invalid and id pass through.

FieldLegend

PropTypeDefault
variant"legend" | "label""legend"

Extends Fieldset.Legend from Ark, and renders the real legend.

FieldSetHelper

Muted supporting text for the group, wired into the fieldset's aria-describedby.

FieldSetError

Destructive message for the group; renders only while the FieldSet is invalid.

On this page