The Field family wraps ZUI's existing form controls — Input, Textarea, Select, Checkbox, Radio and Label — to give every form consistent spacing, helper text, validation messaging and layout.

Forms should always use Field. Label and the form controls carry no built-in margins; Field, FieldGroup and FieldSet own all spacing. Place a label + control on their own and they will sit flush together.

Usage

A Field stacks a label, a control and an optional description. Wire up id/for and aria-describedby yourself — the components are presentational.

We'll never share your email.
<div class="zui-field">
<label class="zui-label" for="email">Email</label>
<input class="zui-input" type="email" id="email" placeholder="you@example.com" aria-describedby="email-desc" />
<div class="zui-field-description" id="email-desc">We'll never share your email.</div>
</div>

Validation

Set invalid on the Field to tint the control and messaging, and add a FieldError (renders role="alert"). Mirror the state on the control with aria-invalid.

<div class="zui-field zui-field-invalid">
<label class="zui-label" for="pw">Password</label>
<input class="zui-input" type="password" id="pw" value="123" aria-invalid="true" aria-describedby="pw-error" />
<div class="zui-field-error" role="alert" id="pw-error">Password must be at least 8 characters.</div>
</div>

Field group

FieldGroup stacks multiple Fields with consistent spacing. It also establishes a query container, which the responsive orientation relies on.

<div class="zui-field-group">
<div class="zui-field">
  <label class="zui-label" for="first">First name</label>
  <input class="zui-input" id="first" type="text" />
</div>
<div class="zui-field">
  <label class="zui-label" for="last">Last name</label>
  <input class="zui-input" id="last" type="text" />
</div>
</div>

Horizontal

Set orientation="horizontal" to place the label beside the control. Descriptions and errors line up under the control.

<div class="zui-field zui-field-horizontal">
<label class="zui-label" for="nick">Nickname</label>
<input class="zui-input" id="nick" type="text" />
</div>

Responsive

orientation="responsive" starts vertical and switches to horizontal once the containing FieldGroup is at least 32rem wide. It must be inside a FieldGroup (that's what establishes the query container). Resize the preview to see it flip.

<div class="zui-field-group">
<div class="zui-field zui-field-responsive">
  <label class="zui-label" for="disp">Display name</label>
  <input class="zui-input" id="disp" type="text" />
</div>
</div>

Fieldset and legend

Group related controls — such as a set of radios or checkboxes — with FieldSet and FieldLegend. The legend's variant="label" matches label sizing for nested fieldsets.

Favourite colour
<fieldset class="zui-field-set">
<legend class="zui-field-legend">Favourite colour</legend>
<div class="zui-radio-list">
  <label class="zui-radio"><input type="radio" name="colour" value="red" /> Red</label>
  <label class="zui-radio"><input type="radio" name="colour" value="blue" checked /> Blue</label>
  <label class="zui-radio"><input type="radio" name="colour" value="green" /> Green</label>
</div>
</fieldset>

Checkbox group

The same pattern groups a set of checkboxes — swap the radios for a zui-checkbox-list.

Interests
<fieldset class="zui-field-set">
<legend class="zui-field-legend">Interests</legend>
<div class="zui-checkbox-list">
  <label class="zui-checkbox"><input type="checkbox" checked /> Music</label>
  <label class="zui-checkbox"><input type="checkbox" /> Sports</label>
  <label class="zui-checkbox"><input type="checkbox" checked /> Travel</label>
</div>
</fieldset>

Grouping fields

FieldSet isn't only for radios and checkboxes — use it to group several related Fields under one legend, with an optional group-level FieldDescription.

Shipping address
Where should we send your order?
<fieldset class="zui-field-set">
<legend class="zui-field-legend">Shipping address</legend>
<div class="zui-field-description">Where should we send your order?</div>
<div class="zui-field-group">
  <div class="zui-field">
    <label class="zui-label" for="street">Street</label>
    <input class="zui-input" id="street" type="text" />
  </div>
  <div class="zui-field">
    <label class="zui-label" for="city">City</label>
    <input class="zui-input" id="city" type="text" />
  </div>
</div>
</fieldset>

Label-sized legend

Set variant="label" on the FieldLegend so it matches label sizing — handy when a FieldSet is nested inside another field.

Contact method
<fieldset class="zui-field-set">
<legend class="zui-field-legend zui-field-legend-label">Contact method</legend>
<div class="zui-radio-list">
  <label class="zui-radio"><input type="radio" name="contact" value="email" checked /> Email</label>
  <label class="zui-radio"><input type="radio" name="contact" value="sms" /> SMS</label>
</div>
</fieldset>

Disabled group

A native disabled attribute on the FieldSet disables every control inside it.

Notifications
<fieldset class="zui-field-set" disabled>
<legend class="zui-field-legend">Notifications</legend>
<div class="zui-checkbox-list">
  <label class="zui-checkbox"><input type="checkbox" checked /> Email</label>
  <label class="zui-checkbox"><input type="checkbox" /> Push</label>
</div>
</fieldset>

Separator

FieldSeparator draws a divider between sections of a FieldGroup. Pass inline content to centre a label on the line.

or
<div class="zui-field-group">
<div class="zui-field">
  <label class="zui-label" for="sep-email">Email</label>
  <input class="zui-input" id="sep-email" type="email" />
</div>
<div class="zui-field-separator">or</div>
<div class="zui-field">
  <label class="zui-label" for="sep-phone">Phone</label>
  <input class="zui-input" id="sep-phone" type="tel" />
</div>
</div>

Components

Component Element Description
Field div[role="group"] Wraps a label, control and optional description/error. Owns spacing.
FieldGroup div Stacks multiple Fields and establishes the container for responsive orientation.
FieldDescription div Helper text for a field.
FieldError div[role="alert"] Validation message.
FieldSet fieldset Groups related controls (e.g. radios, checkboxes).
FieldLegend legend Caption for a FieldSet.
FieldSeparator div[role="separator"] Divider with optional inline content.

Field props

Prop Type Default Description
orientation 'vertical' | 'horizontal' | 'responsive' 'vertical' Layout direction. responsive requires a FieldGroup ancestor.
invalid boolean false Applies invalid styling to the field, control border and messaging.

FieldLegend props

Prop Type Default Description
variant 'legend' | 'label' 'legend' label matches label sizing — useful for nested fieldsets.

CSS classes

Class Description
.zui-field Field wrapper (vertical).
.zui-field-horizontal Label beside control.
.zui-field-responsive Vertical until the container reaches 32rem, then horizontal.
.zui-field-invalid Invalid state styling.
.zui-field-group Stacks fields; query container.
.zui-field-description Helper text.
.zui-field-error Validation message.
.zui-field-set Fieldset reset + spacing.
.zui-field-legend Legend styling.
.zui-field-legend-label Smaller, label-sized legend.
.zui-field-separator Divider with optional inline content.

Theme

Copy this CSS to your project: