Form elements are styled globally using semantic tokens. All text inputs, textareas, selects, checkboxes, and radios share consistent sizing, focus states, and disabled styling.


Form Tokens

All form styling is controlled by semantic tokens in :root:

Token Default Purpose
Default border color
Input background
Input text color
Placeholder text color
Focus border and ring color
Disabled background
Disabled text color
Checkbox unchecked background
Checkbox/radio checked fill
Checkbox/radio border color
Checkmark/dot color
Toggle track background
Toggle knob (unchecked)
Toggle track (checked)
Toggle knob (checked)

Labels

Labels are styled as block elements with medium weight:

<label for="name">Full name</label>
<input type="text" id="name" placeholder="Enter your name">

Properties: display: block, font-size: var(--font-s), font-weight: var(--font-weight-medium), bottom margin for spacing from the input.


Text Inputs

All standard text input types are styled globally:

<input type="text" placeholder="Text">
<input type="email" placeholder="Email">
<input type="password" placeholder="Password">
<input type="number" placeholder="Number">
<input type="search" placeholder="Search">
<input type="url" placeholder="URL">
<input type="tel" placeholder="Phone">

Properties: full width, font-size: var(--font-m) (matches body text), padding: var(--space-m) var(--space-l), border from , smooth focus transition.


Focus State

All inputs share a consistent focus style:

  • Border color changes to
  • A subtle box-shadow ring appears (2px, 75% transparent)
  • No outline (replaced by box-shadow for consistency)

This is accessibility-safe and keyboard-visible.


Textarea

Textareas have a minimum height and allow vertical resizing:

<label for="message">Message</label>
<textarea id="message" placeholder="Enter your message..."></textarea>

Properties: min-height: 120px, resize: vertical.


Select

Selects use a custom dropdown arrow via an inline SVG background:

<label for="country">Country</label>
<select id="country">
  <option value="" disabled selected>Choose a country</option>
  <option value="nz">New Zealand</option>
  <option value="au">Australia</option>
</select>

Properties: appearance: none, custom caret, right padding for arrow space.


Colour Input

The native colour picker is styled to match other form inputs. Browser chrome is removed so the colour swatch fills the entire element. Use alongside a text input for hex/named colour entry.

<div class="block row gap-s align-center">
  <input type="color" id="color-picker" value="#ffa500">
  <input type="text" value="ffa500" placeholder="hex or name">
</div>

Properties: appearance: none, aspect-ratio: 1 / 1, align-self: stretch (matches sibling height), padding: var(--space-xs), swatch wrapper padding removed. Same border and focus styles as text inputs.


Disabled State

Add the disabled attribute to any input, textarea, or select:

<input type="text" value="Cannot edit" disabled>

Properties: background, color, cursor: not-allowed.


Checkbox & Radio

Checkboxes and radios use appearance: none with custom styling. Wrap each in for inline label alignment.

Checkbox

<div class="form-check">
  <input type="checkbox" id="terms">
  <label for="terms">I agree to the terms</label>
</div>

Properties: 24px size (), fill, border with corners. Checked state uses fill with a white checkmark SVG.

Radio

<div class="form-check">
  <input type="radio" name="group" id="option-a">
  <label for="option-a">Option A</label>
</div>

Properties: same as checkbox but with border-radius: 50% and a centered dot on checked.


Toggle / Switch

A toggle is a checkbox styled as a sliding switch. Use instead of . Always include role="switch" for accessibility.

Default (label left)

<div class="form-toggle">
  <input type="checkbox" id="notifications" role="switch">
  <label for="notifications">Enable notifications</label>
</div>

Label right

Add to place the label after the toggle.

<div class="form-toggle is-label-right">
  <input type="checkbox" id="darkmode" role="switch">
  <label for="darkmode">Dark mode</label>
</div>

Disabled

<div class="form-toggle">
  <input type="checkbox" id="feature" role="switch" disabled>
  <label for="feature">Coming soon</label>
</div>

Properties: 44px × 24px pill-shaped track, 18px circular knob. Unchecked: background with knob. Checked: background, knob slides right and becomes .


Layout Patterns

Form Group

Use to wrap a label + input pair with consistent bottom spacing:

<div class="form-group">
  <label for="name">Name</label>
  <input type="text" id="name">
</div>
<div class="form-group">
  <label for="email">Email</label>
  <input type="email" id="email">
</div>

Form Check

Use for inline checkbox/radio + label pairs:

<div class="form-check">
  <input type="checkbox" id="opt-in">
  <label for="opt-in">Subscribe to newsletter</label>
</div>

Properties: display: flex, align-items: center, gap for spacing. The label inside is inline with regular weight.

Fieldset & Legend

Use <fieldset> and <legend> to group related form controls:

Contact details
<fieldset>
  <legend>Contact details</legend>
  <div class="form-group">
    <label for="phone">Phone</label>
    <input type="tel" id="phone">
  </div>
</fieldset>

Segmented Control

is a button group that acts like a single-select input — similar to a group of radio buttons but with a tab-like appearance.

<div class="segmented-control">
  <button class="segmented-control-btn is-active">Option A</button>
  <button class="segmented-control-btn">Option B</button>
  <button class="segmented-control-btn">Option C</button>
</div>

Icon variant

Use on a segment button for icon-only options:

<button class="segmented-control-btn is-icon" aria-label="Grid view">
  <div class="svg-icn" data-icon="grid"><!-- SVG --></div>
</button>

Styling

  • Track: with corners
  • Active segment: background with text
  • Focus: 2px outline with offset

Accessibility

  • Add role="group" and aria-label to the container
  • Use aria-pressed="true" on the active segment button
  • Toggle is-active and aria-pressed via JavaScript on click

Slider

input[type="range"] is styled with design system tokens. Use for a labelled slider with live value display. See the Slider docs for full details.

75%
<div class="slider-wrapper">
  <div class="slider-header">
    <label for="opacity">Opacity</label>
    <span class="slider-value" id="opacity-val">75%</span>
  </div>
  <input type="range" id="opacity" min="0" max="100" value="75"
    oninput="document.getElementById('opacity-val').textContent = this.value + '%'">
</div>

Number Input

wraps a native number input with decrement/increment buttons. See the Number Input docs for full details.

<div class="number-input" role="group" aria-label="Quantity">
  <button class="number-input-btn" data-number-decrement type="button" aria-label="Decrease">&minus;</button>
  <input type="number" value="1" min="0" max="99" aria-label="Quantity">
  <button class="number-input-btn" data-number-increment type="button" aria-label="Increase">+</button>
</div>

Requires assets/js/number-input.js.


Radio Group

wraps multiple radio inputs with consistent spacing. Supports vertical (default) and horizontal () layouts. See the Radio Group docs for full details.

Shipping method
<fieldset>
  <legend class="radio-group-label">Shipping method</legend>
  <div class="radio-group">
    <div class="form-check">
      <input type="radio" name="shipping" id="std" checked>
      <label for="std">Standard (5-7 days)</label>
    </div>
    ...
  </div>
</fieldset>

Rules

Do Don't
Use <label> with for attribute Use placeholder as a label replacement
Use for spacing Add margins directly to inputs
Use for checkbox/radio pairs Float labels next to checkboxes manually
Use for toggle switches Style a checkbox as a toggle without the wrapper
Add role="switch" on toggle inputs Use a toggle without the switch role
Use semantic tokens for customization Hardcode colors on individual inputs
Use <fieldset> for logical grouping Use <div> with borders to fake fieldsets
Keep inputs full-width by default Set fixed widths unless layout requires it

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback