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" value="Ada Lovelace"/>
</label>
<label>
<span>Email address</span>
<input class="form-input" type="email" name="email" autocomplete="email" placeholder="you@example.com"/>
</label>
<label>
<span>Update frequency</span>
<select class="form-select" name="updates">
<option>Weekly summary</option>
<option>Important changes only</option>
<option>Never</option>
</select>
</label>
<label>
<span>Short bio</span>
<textarea class="form-textarea" name="bio" rows="4" placeholder="What are you working on?">
</textarea>
</label>
<layout-cluster gap="var(--space-sm)">
<button class="form-button" type="button">Save settings</button>
<button class="form-button btn-secondary" type="button">Cancel</button>
</layout-cluster>
</layout-stack>
</form> <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" value="My Project" aria-invalid="true" aria-describedby="slug-error"/>
<small id="slug-error">Use lowercase letters, numbers, and hyphens only.</small>
</label> <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-inputon text-like native inputs.form-selectonselect.form-textareaontextarea.form-buttononbuttonor 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
typeandautocompletevalue. - Connect hints and errors with
aria-describedby. - Set
aria-invalidonly after validation determines the value is invalid. - Keep a real submit button inside forms that submit data.
- Group related checkboxes or radios with
fieldsetandlegend.