Forms
stable
Form element styles ship as bare element selectors — no extra classes required on
input, textarea, or select. All styles consume
Tier-3 --input-* tokens so the full form palette can be rethemed in one
place without touching semantic tokens.
Elements
Apply the styles by including farn-components.css. Every input,
textarea, and select on the page is styled automatically.
Use .form-field to group a label with its control.
<div class="form-field">
<label for="name">Name</label>
<input type="text" id="name" placeholder="Jane Smith" />
</div>
<div class="form-field">
<label for="role">Role</label>
<select id="role">
<option value="">Choose a role</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-field">
<label for="bio">Bio</label>
<textarea id="bio"></textarea>
</div> States
Hover darkens the background to --input-bg-active. Focus adds a coloured
border and a soft glow ring driven by --input-focus-shadow. The global
:focus-visible outline from base.css is suppressed in favour of
the token-driven focus ring. Error state can be set either via aria-invalid="true"
on the input (preferred — screen readers announce the invalid state automatically) or via
.form-field--error on the wrapper (colours the label and hint text too).
Disabled inputs reduce opacity to --input-disabled-opacity and suppress pointer events.
<!-- Error via ARIA attribute — announced by screen readers -->
<input type="email" aria-invalid="true" value="bad@" />
<!-- Error via wrapper — colours label and hint text too -->
<div class="form-field form-field--error">
<label for="email">Email</label>
<input type="email" id="email" aria-invalid="true" value="bad@" />
<span class="form-hint">Enter a valid email address.</span>
</div>
<!-- Disabled -->
<input type="text" disabled value="Cannot edit" /> Wrapper & hint text
.form-field is a flex column that stacks label, control, and optional hint
text with consistent spacing. .form-hint renders helper or error text below
the control in a smaller, secondary colour.
<div class="form-field">
<label for="username">Username</label>
<input type="text" id="username" />
<span class="form-hint">3–24 characters.</span>
</div>
<!-- Error variant — hint text turns red automatically -->
<div class="form-field form-field--error">
<label for="email">Email</label>
<input type="email" id="email" aria-invalid="true" />
<span class="form-hint">Enter a valid email address.</span>
</div> Token reference
All --input-* tokens are defined in tokens/components.css.
| Token | Default | Purpose |
|---|---|---|
--input-radius | var(--radius-md) | Border radius for all form controls |
--input-bg | var(--color-bg-panel) | Idle background |
--input-bg-active | var(--color-bg-inset) | Hover and focus background |
--input-border | transparent | Idle border colour (invisible by default) |
--input-border-focus | var(--color-accent) | Focus border colour |
--input-border-error | var(--color-error) | Error border colour |
--input-focus-shadow | 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent) | Focus glow ring |
--input-text | var(--color-text) | Input text colour |
--input-placeholder | var(--color-text-tertiary) | Placeholder text colour |
--input-disabled-opacity | 0.4 | Opacity applied to disabled controls |
Overriding tokens
Override tokens on a scoped selector to retheme form controls in one section without
affecting the rest of the page. Because --input-* tokens sit above the
semantic layer, changes here have no effect on other elements that share the same
semantic colour.
/* Tighter radius for a dense data-entry panel */
.data-panel {
--input-radius: var(--radius-sm);
}
/* Always show a visible idle border in a section with no panel background */
.flat-form {
--input-bg: transparent;
--input-border: var(--color-border);
} Anatomy
<!-- Minimal — element selector handles all styling -->
<input type="text" placeholder="…" />
<!-- With label -->
<label for="x">Label</label>
<input type="text" id="x" />
<!-- Full wrapper pattern -->
<div class="form-field">
<label for="y">Label</label>
<input type="text" id="y" />
<span class="form-hint">Helper text.</span>
</div>