Forms

Accessible native form controls styled through focused class hooks, with labels, validation, and layout recipes.

The form layer styles native controls through .form-input, .form-select, .form-textarea, and .form-button. Use real input, select, textarea, and button elements so browser behavior and accessibility remain intact. The older .input-field, .input, .select, .textarea, and .form-control hooks remain compatible and now use the same tokens and states.

Complete settings form

Profile settings

Tab through the form to see the shipped focus states.

<form>
<layout-stack gap="var(--space-md)">
<label>
<span>Display name</span>
<input class="form-input" name="display-name" autocomplete="name">
</label>
<label>
<span>Email address</span>
<input class="form-input" type="email" name="email" autocomplete="email">
</label>
<label>
<span>Update frequency</span>
<select class="form-select" name="updates">
<option>Weekly summary</option>
<option>Important changes only</option>
</select>
</label>
<button class="form-button stack-intrinsic" type="submit">Save settings</button>
</layout-stack>
</form>

Validation

Native constraint attributes provide browser validation. Add aria-invalid="true" when your application knows a value is invalid, connect the explanation with aria-describedby, and use .input-error for the current visual state.

Invalid field

<label>
<span>Project slug</span>
<input
class="form-input input-error"
name="slug"
aria-invalid="true"
aria-describedby="slug-error"
>
<small id="slug-error">Use lowercase letters, numbers, and hyphens only.</small>
</label>

The available visual state classes are .input-error and .input-success. They do not set ARIA state for you.

Button variants

Button colors

The .btn-primary, .btn-secondary, .btn-success, .btn-warning, and .btn-error variants share one state contract. They work on native buttons, .button/.btn hosts, and .form-button, including hover, active, and disabled states. Set --button-variant-background and --button-variant-color for a local palette, then optionally override --button-variant-hover-background, --button-variant-active-background, or --button-variant-disabled-background.

Public API

Use the role-specific classes when you can:

  • .form-input on text-like native inputs
  • .form-select on select
  • .form-textarea on textarea
  • .form-button on button or button-like inputs

For attribute-oriented markup, [data-form-control] applies the same field contract; the native element supplies select and textarea behavior. The legacy .input-field, .input, .select, .textarea, and .form-control aliases are retained for compatibility. Custom tags such as <form-input> are not shipped: keeping the native control preserves its built-in semantics and behavior.

<input class="form-input" aria-label="Canonical class">
<input data-form-control aria-label="Data attribute">
<input class="input-field" aria-label="Legacy alias">

In browsers with customizable selects, .form-select keeps the selected value and picker icon aligned on one line. Override --form-select-icon-gap when the icon needs more or less separation from the value.

Useful tokens

  • --form-control-padding-block, --form-control-padding-inline
  • --form-control-background, --form-control-color, --form-control-border-color
  • --form-control-radius, --form-control-hover-border-color
  • --form-control-focus-border-color, --form-control-focus-shadow
  • --form-control-disabled-background, --form-control-disabled-color, --form-control-disabled-opacity
  • --form-textarea-min-block-size, --form-textarea-resize
  • --form-button-background, --form-button-color
  • --form-select-icon-gap
  • --surface-default, --text-default, --text-muted
  • --outline-default, --outline-overt, --outline-focus
  • --radius-md, --border-width
  • --focus-ring-width, --focus-ring-color, --focus-ring-offset
  • --accent, --accent-overt, --accent-muted

Accessibility checklist

  • Give every control a visible label; placeholders are examples, not labels.
  • Use the correct input type and autocomplete value.
  • Connect hints and errors with aria-describedby.
  • Set aria-invalid only after validation determines the value is invalid.
  • Keep a real submit button inside forms that submit data.
  • Group related checkboxes or radios with fieldset and legend.