Forms

Forms allow users to enter data and interact with the system. Standard provides a complete set of form controls including inputs, textareas, selects, checkboxes, radio buttons, switches, and file uploads — all with built-in validation states and accessibility.


Installation

Copy the form CSS from styles/docs.css or include the Standard stylesheet:

<link rel="stylesheet" href="standard.min.css">

Then use form classes in your HTML:

<div class="FormField">
    <label class="FormField-label" for="email">Email</label>
    <input type="email" id="email" class="Input" placeholder="you@example.com">
</div>

Usage

The base .Input class provides core input styling. Wrap inputs in .FormField for proper labeling and spacing.

<div class="FormField" style="max-width: 320px;">
<label class="FormField-label" for="demo-input">Username</label>
<input type="text" id="demo-input" class="Input" placeholder="Enter username">
</div>

Input Variants

Sizes

Inputs come in three sizes: small, medium (default), and large.

<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 320px;">
<input type="text" class="Input Input--small" placeholder="Small">
<input type="text" class="Input" placeholder="Medium (default)">
<input type="text" class="Input Input--large" placeholder="Large">
</div>

With Icons

Add leading or trailing icons for visual context.

<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 320px;">
<div class="Input-wrapper">
<i class="ph ph-magnifying-glass Input-icon"></i>
<input type="text" class="Input Input--withIcon" placeholder="Search...">
</div>
<div class="Input-wrapper">
<input type="email" class="Input Input--withIconTrailing" placeholder="Enter email">
<i class="ph ph-envelope Input-icon Input-icon--trailing"></i>
</div>
</div>

With Button

Common pattern for search or subscribe fields.

<div class="Input-group" style="max-width: 400px;">
<input type="text" class="Input Input--grouped" placeholder="Search documentation...">
<button class="Button Button--primary">Search</button>
</div>

Password with Toggle

Toggle password visibility for user convenience.

<div class="FormField" style="max-width: 320px;">
<label class="FormField-label" for="pw-demo">Password</label>
<div class="Input-wrapper">
<input type="password" id="pw-demo" class="Input Input--withIconTrailing" placeholder="Enter password">
<button type="button" class="Input-toggle" aria-label="Toggle password visibility" onclick="const input = this.previousElementSibling; const icon = this.querySelector('i'); if(input.type === 'password') { input.type = 'text'; icon.className = 'ph ph-eye-slash'; } else { input.type = 'password'; icon.className = 'ph ph-eye'; }">
<i class="ph ph-eye"></i>
</button>
</div>
</div>

States

Validation States

Visual feedback for different input states.

This field is required
Looks good!
<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 320px;">
<div class="FormField">
<label class="FormField-label">Default</label>
<input type="text" class="Input" value="Normal input">
</div>
<div class="FormField FormField--error">
<label class="FormField-label">Error</label>
<input type="text" class="Input Input--error" value="Invalid value">
<span class="FormField-message FormField-message--error">
<i class="ph ph-warning-circle"></i>
This field is required
</span>
</div>
<div class="FormField FormField--success">
<label class="FormField-label">Success</label>
<input type="text" class="Input Input--success" value="Valid input">
<span class="FormField-message FormField-message--success">
<i class="ph ph-check-circle"></i>
Looks good!
</span>
</div>
<div class="FormField">
<label class="FormField-label">Disabled</label>
<input type="text" class="Input" value="Cannot edit" disabled>
</div>
</div>

Helper Text & Character Count

Provide additional context or show remaining characters.

We'll never share your email.
<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 320px;">
<div class="FormField">
<label class="FormField-label" for="helper-demo">Email Address</label>
<input type="email" id="helper-demo" class="Input" placeholder="you@example.com">
<span class="FormField-helper">We'll never share your email.</span>
</div>
<div class="FormField">
<label class="FormField-label" for="count-demo">Bio</label>
<input type="text" id="count-demo" class="Input" placeholder="Tell us about yourself" maxlength="100">
<div class="FormField-footer">
<span class="FormField-helper">Brief description</span>
<span class="FormField-count">0/100</span>
</div>
</div>
</div>

Required Fields

Indicate required fields with an asterisk.

<div class="FormField" style="max-width: 320px;">
<label class="FormField-label FormField-label--required" for="req-demo">Email</label>
<input type="email" id="req-demo" class="Input" placeholder="you@example.com" required>
</div>

Textarea

Multi-line text input for longer content.

<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 400px;">
<div class="FormField">
<label class="FormField-label" for="ta-basic">Description</label>
<textarea id="ta-basic" class="Textarea" placeholder="Enter a detailed description..." rows="4"></textarea>
</div>
<div class="FormField">
<label class="FormField-label" for="ta-count">Bio</label>
<textarea id="ta-count" class="Textarea" placeholder="Tell us about yourself..." rows="3" maxlength="280"></textarea>
<div class="FormField-footer">
<span class="FormField-helper">Brief description for your profile</span>
<span class="FormField-count">0/280</span>
</div>
</div>
</div>

Select

Dropdown for choosing from predefined options.

<div class="FormField" style="max-width: 320px;">
<label class="FormField-label" for="select-demo">Country</label>
<select id="select-demo" class="Select">
<option value="">Choose a country...</option>
<option>United States</option>
<option>Canada</option>
<option>United Kingdom</option>
<option>Australia</option>
<option>Germany</option>
</select>
</div>

Checkbox

For binary choices or multiple selections.

<div style="display: flex; flex-direction: column; gap: var(--space-3);">
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Remember me</span>
</label>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" checked>
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Receive notifications</span>
</label>
<label class="Checkbox Checkbox--disabled">
<input type="checkbox" class="Checkbox-input" disabled>
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Disabled option</span>
</label>
</div>

Checkbox Group

Group related checkboxes in a fieldset.

Interests
<fieldset class="FormFieldset">
<legend class="FormFieldset-legend">Interests</legend>
<div class="CheckboxGroup">
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" name="interests" value="design">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Design</span>
</label>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" name="interests" value="development" checked>
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Development</span>
</label>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" name="interests" value="marketing">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Marketing</span>
</label>
</div>
</fieldset>

Indeterminate State

For “select all” patterns where some items are selected.

<div style="max-width: 300px;">
<label class="Checkbox" style="margin-bottom: var(--space-3); border-bottom: 1px solid var(--bd); padding-bottom: var(--space-3);">
<input type="checkbox" class="Checkbox-input Checkbox-input--indeterminate">
<span class="Checkbox-box Checkbox-box--indeterminate"></span>
<span class="Checkbox-label" style="font-weight: 500;">Select All Files</span>
</label>
<div style="padding-left: var(--space-6); display: flex; flex-direction: column; gap: var(--space-3);">
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" checked>
<span class="Checkbox-box"></span>
<span class="Checkbox-label">document.pdf</span>
</label>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">image.png</span>
</label>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input" checked>
<span class="Checkbox-box"></span>
<span class="Checkbox-label">spreadsheet.xlsx</span>
</label>
</div>
</div>

Radio Buttons

For single selection from a list of options.

Plan
<fieldset class="FormFieldset">
<legend class="FormFieldset-legend">Plan</legend>
<div class="RadioGroup">
<label class="Radio">
<input type="radio" class="Radio-input" name="plan" value="free">
<span class="Radio-circle"></span>
<span class="Radio-label">Free</span>
</label>
<label class="Radio">
<input type="radio" class="Radio-input" name="plan" value="pro" checked>
<span class="Radio-circle"></span>
<span class="Radio-label">Pro</span>
</label>
<label class="Radio">
<input type="radio" class="Radio-input" name="plan" value="enterprise">
<span class="Radio-circle"></span>
<span class="Radio-label">Enterprise</span>
</label>
</div>
</fieldset>

Radio Cards

Richer radio selection with descriptions and prices.

<div class="RadioCardGroup" style="max-width: 400px;">
<label class="RadioCard">
<input type="radio" name="tier" value="starter" class="RadioCard-input">
<div class="RadioCard-content">
<div class="RadioCard-header">
<span class="RadioCard-title">Starter</span>
<span class="RadioCard-price">$9/mo</span>
</div>
<p class="RadioCard-description">Perfect for individuals and small projects.</p>
</div>
</label>
<label class="RadioCard RadioCard--selected">
<input type="radio" name="tier" value="pro" class="RadioCard-input" checked>
<div class="RadioCard-content">
<div class="RadioCard-header">
<span class="RadioCard-title">Pro</span>
<span class="RadioCard-price">$29/mo</span>
</div>
<p class="RadioCard-description">For growing teams with advanced needs.</p>
</div>
</label>
<label class="RadioCard">
<input type="radio" name="tier" value="enterprise" class="RadioCard-input">
<div class="RadioCard-content">
<div class="RadioCard-header">
<span class="RadioCard-title">Enterprise</span>
<span class="RadioCard-price">Custom</span>
</div>
<p class="RadioCard-description">Dedicated support and custom integrations.</p>
</div>
</label>
</div>

Switch

Toggle switches for on/off settings.

<div style="display: flex; flex-direction: column; gap: var(--space-4);">
<label class="Switch">
<input type="checkbox" class="Switch-input">
<span class="Switch-track"></span>
<span class="Switch-label">Enable notifications</span>
</label>
<label class="Switch">
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
<span class="Switch-label">Dark mode</span>
</label>
<label class="Switch Switch--disabled">
<input type="checkbox" class="Switch-input" disabled>
<span class="Switch-track"></span>
<span class="Switch-label">Disabled switch</span>
</label>
</div>

Switch with Description

For settings that need more context.

<div style="max-width: 400px;">
<label class="Switch Switch--block">
<div class="Switch-content">
<span class="Switch-label">Email notifications</span>
<span class="Switch-description">Receive email updates about your account activity</span>
</div>
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
</label>
</div>

Switch Sizes

<div style="display: flex; flex-direction: column; gap: var(--space-4);">
<label class="Switch Switch--small">
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
<span class="Switch-label">Small</span>
</label>
<label class="Switch">
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
<span class="Switch-label">Medium (default)</span>
</label>
<label class="Switch Switch--large">
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
<span class="Switch-label">Large</span>
</label>
</div>

File Upload

File input with drag-and-drop support.

<div class="FormField" style="max-width: 400px;">
<label class="FormField-label">Attachment</label>
<div class="FileInput">
<input type="file" class="FileInput-input" id="file-demo">
<label for="file-demo" class="FileInput-label">
<i class="ph ph-upload-simple FileInput-icon"></i>
<span class="FileInput-text">Choose file or drag here</span>
<span class="FileInput-hint">PNG, JPG up to 10MB</span>
</label>
</div>
</div>

File Upload with Preview

Show a preview thumbnail alongside the upload button.

Preview
JPG, PNG or GIF. Max 2MB.
<div class="FormField" style="max-width: 400px;">
<label class="FormField-label">Profile Photo</label>
<div class="FileInput FileInput--with-preview">
<div class="FileInput-preview">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=upload" alt="Preview" class="FileInput-image">
</div>
<div class="FileInput-content">
<input type="file" class="FileInput-input" id="file-preview-demo" accept="image/*">
<label for="file-preview-demo" class="Button Button--secondary Button--small">
<i class="ph ph-upload-simple Button-icon"></i>
Change Photo
</label>
<span class="FileInput-hint">JPG, PNG or GIF. Max 2MB.</span>
</div>
</div>
</div>

Multiple File Upload

Upload multiple files with a file list display.

document.pdf 2.4 MB
photo.jpg 1.1 MB
<div class="FormField" style="max-width: 400px;">
<label class="FormField-label">Documents</label>
<div class="FileInput FileInput--multiple">
<input type="file" class="FileInput-input" id="file-multi-demo" multiple>
<label for="file-multi-demo" class="FileInput-label">
<i class="ph ph-files FileInput-icon"></i>
<span class="FileInput-text">Drop files here or click to browse</span>
<span class="FileInput-hint">Upload multiple files at once</span>
</label>
</div>
<div class="FileInput-list" style="margin-top: var(--space-3);">
<div class="FileInput-item">
<i class="ph ph-file-pdf FileInput-item-icon" style="color: oklch(55% 0.2 25);"></i>
<span class="FileInput-item-name">document.pdf</span>
<span class="FileInput-item-size">2.4 MB</span>
<button class="Button Button--icon Button--ghost Button--small" aria-label="Remove file">
<i class="ph ph-x"></i>
</button>
</div>
<div class="FileInput-item">
<i class="ph ph-file-image FileInput-item-icon" style="color: oklch(55% 0.15 150);"></i>
<span class="FileInput-item-name">photo.jpg</span>
<span class="FileInput-item-size">1.1 MB</span>
<button class="Button Button--icon Button--ghost Button--small" aria-label="Remove file">
<i class="ph ph-x"></i>
</button>
</div>
</div>
</div>

Form Layouts

Stacked Form (Default)

Vertical layout with labels above inputs.

<form style="max-width: 400px; display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormField">
<label class="FormField-label FormField-label--required">Full Name</label>
<input type="text" class="Input" placeholder="John Doe">
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Email</label>
<input type="email" class="Input" placeholder="john@example.com">
</div>
<div class="FormField">
<label class="FormField-label">Company</label>
<input type="text" class="Input" placeholder="Acme Inc">
</div>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Subscribe to newsletter</span>
</label>
<button type="submit" class="Button Button--primary Button--block">Submit</button>
</form>

Inline Form

Horizontal layout for compact forms like newsletter signups.

<form class="FormInline">
<div class="FormField" style="flex: 1;">
<input type="email" class="Input" placeholder="Enter your email">
</div>
<button type="submit" class="Button Button--primary">Subscribe</button>
</form>

Two-Column Form

Side-by-side fields for related data.

<form style="max-width: 500px;">
<div class="FormRow">
<div class="FormField">
<label class="FormField-label">First Name</label>
<input type="text" class="Input" placeholder="John">
</div>
<div class="FormField">
<label class="FormField-label">Last Name</label>
<input type="text" class="Input" placeholder="Doe">
</div>
</div>
<div class="FormField" style="margin-top: var(--space-4);">
<label class="FormField-label">Email</label>
<input type="email" class="Input" placeholder="john.doe@example.com">
</div>
</form>

Common Patterns

Login Form

Welcome back

Sign in to your account

Forgot password?
Don't have an account? Sign up
<div class="Card" style="max-width: 400px; margin: 0 auto;">
<div class="Card-body">
<div style="text-align: center; margin-bottom: var(--space-6);">
<h3 style="margin: 0 0 var(--space-2);">Welcome back</h3>
<p style="color: var(--fg-3); margin: 0; font-size: 0.9rem;">Sign in to your account</p>
</div>
<form style="display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormField">
<label class="FormField-label FormField-label--required">Email</label>
<div class="Input-wrapper">
<i class="ph ph-envelope Input-icon"></i>
<input type="email" class="Input Input--withIcon" placeholder="you@example.com">
</div>
</div>
<div class="FormField">
<div style="display: flex; justify-content: space-between; align-items: center;">
<label class="FormField-label FormField-label--required">Password</label>
<a href="#" class="Link" style="font-size: 0.8rem;">Forgot password?</a>
</div>
<div class="Input-wrapper">
<input type="password" class="Input Input--withIconTrailing" placeholder="••••••••">
<button type="button" class="Input-toggle" aria-label="Show password">
<i class="ph ph-eye"></i>
</button>
</div>
</div>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input">
<span class="Checkbox-box"></span>
<span class="Checkbox-label">Remember me for 30 days</span>
</label>
<button type="submit" class="Button Button--primary Button--block">Sign In</button>
</form>
<div style="text-align: center; margin-top: var(--space-6); padding-top: var(--space-4); border-top: 1px solid var(--bd);">
<span style="color: var(--fg-3); font-size: 0.9rem;">Don't have an account? </span>
<a href="#" class="Link">Sign up</a>
</div>
</div>
</div>

Signup Form

Create your account

Start your 14-day free trial

Must be at least 8 characters with a number and symbol
<div class="Card" style="max-width: 480px; margin: 0 auto;">
<div class="Card-body">
<div style="text-align: center; margin-bottom: var(--space-6);">
<h3 style="margin: 0 0 var(--space-2);">Create your account</h3>
<p style="color: var(--fg-3); margin: 0; font-size: 0.9rem;">Start your 14-day free trial</p>
</div>
<form style="display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormRow">
<div class="FormField">
<label class="FormField-label FormField-label--required">First name</label>
<input type="text" class="Input" placeholder="John">
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Last name</label>
<input type="text" class="Input" placeholder="Doe">
</div>
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Email</label>
<input type="email" class="Input" placeholder="john@example.com">
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Password</label>
<input type="password" class="Input" placeholder="Create a strong password">
<span class="FormField-helper">Must be at least 8 characters with a number and symbol</span>
</div>
<label class="Checkbox">
<input type="checkbox" class="Checkbox-input">
<span class="Checkbox-box"></span>
<span class="Checkbox-label" style="font-size: 0.85rem;">I agree to the <a href="#" class="Link">Terms of Service</a> and <a href="#" class="Link">Privacy Policy</a></span>
</label>
<button type="submit" class="Button Button--primary Button--block">Create Account</button>
</form>
</div>
</div>

Settings Form

Profile Information
Notifications
<form style="max-width: 600px;">
<fieldset class="FormFieldset" style="margin-bottom: var(--space-6);">
<legend class="FormFieldset-legend" style="font-size: 1rem; margin-bottom: var(--space-4);">Profile Information</legend>
<div style="display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormRow">
<div class="FormField">
<label class="FormField-label">First name</label>
<input type="text" class="Input" value="Sarah">
</div>
<div class="FormField">
<label class="FormField-label">Last name</label>
<input type="text" class="Input" value="Chen">
</div>
</div>
<div class="FormField">
<label class="FormField-label">Email</label>
<input type="email" class="Input" value="sarah@example.com">
</div>
<div class="FormField">
<label class="FormField-label">Bio</label>
<textarea class="Textarea" rows="3" placeholder="Tell us about yourself..."></textarea>
<div class="FormField-footer">
<span class="FormField-helper">Brief description for your profile</span>
<span class="FormField-count">0/160</span>
</div>
</div>
</div>
</fieldset>
<fieldset class="FormFieldset" style="margin-bottom: var(--space-6);">
<legend class="FormFieldset-legend" style="font-size: 1rem; margin-bottom: var(--space-4);">Notifications</legend>
<div style="display: flex; flex-direction: column; gap: var(--space-4);">
<label class="Switch Switch--block">
<div class="Switch-content">
<span class="Switch-label">Email notifications</span>
<span class="Switch-description">Receive emails about account activity</span>
</div>
<input type="checkbox" class="Switch-input" checked>
<span class="Switch-track"></span>
</label>
<label class="Switch Switch--block">
<div class="Switch-content">
<span class="Switch-label">Push notifications</span>
<span class="Switch-description">Receive push notifications on your devices</span>
</div>
<input type="checkbox" class="Switch-input">
<span class="Switch-track"></span>
</label>
</div>
</fieldset>
<div style="display: flex; gap: var(--space-3); justify-content: flex-end; padding-top: var(--space-4); border-top: 1px solid var(--bd);">
<button type="button" class="Button Button--secondary">Cancel</button>
<button type="submit" class="Button Button--primary">Save Changes</button>
</div>
</form>

Contact Form

Get in touch

<form class="Card" style="max-width: 500px;">
<div class="Card-header">
<h4 class="Card-title">Get in touch</h4>
</div>
<div class="Card-body" style="display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormRow">
<div class="FormField">
<label class="FormField-label FormField-label--required">Name</label>
<input type="text" class="Input" placeholder="Your name">
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Email</label>
<input type="email" class="Input" placeholder="you@example.com">
</div>
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Subject</label>
<select class="Select">
<option value="">Select a topic...</option>
<option>General Inquiry</option>
<option>Technical Support</option>
<option>Billing Question</option>
<option>Partnership</option>
</select>
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Message</label>
<textarea class="Textarea" rows="5" placeholder="How can we help?"></textarea>
</div>
</div>
<div class="Card-footer">
<button type="submit" class="Button Button--primary">
<i class="ph ph-paper-plane-right Button-icon"></i>
Send Message
</button>
</div>
</form>

Form Validation

Please enter a valid email address
Strong password
<form style="max-width: 400px; display: flex; flex-direction: column; gap: var(--space-4);">
<div class="FormField FormField--error">
<label class="FormField-label FormField-label--required">Email</label>
<input type="email" class="Input Input--error" value="invalid-email">
<span class="FormField-message FormField-message--error">
<i class="ph ph-warning-circle"></i>
Please enter a valid email address
</span>
</div>
<div class="FormField FormField--success">
<label class="FormField-label FormField-label--required">Password</label>
<input type="password" class="Input Input--success" value="••••••••">
<span class="FormField-message FormField-message--success">
<i class="ph ph-check-circle"></i>
Strong password
</span>
</div>
<div class="FormField">
<label class="FormField-label FormField-label--required">Confirm Password</label>
<input type="password" class="Input" placeholder="Re-enter password">
</div>
<button type="submit" class="Button Button--primary Button--block" disabled>Create Account</button>
</form>

Customization

Override form styles using CSS custom properties:

/* Custom input styling */
.Input {
  --input-radius: var(--space-2);
  --input-border: var(--bd-s);
  --input-focus: var(--accent);
  
  border-radius: var(--input-radius);
  border-color: var(--input-border);
}

.Input:focus {
  border-color: var(--input-focus);
  box-shadow: 0 0 0 2px oklch(60% 0.15 250 / 0.15);
}

/* Custom checkbox color */
.Checkbox-input:checked + .Checkbox-box {
  background-color: oklch(55% 0.2 150); /* Green */
  border-color: oklch(55% 0.2 150);
}

/* Custom switch color */
.Switch-input:checked + .Switch-track {
  background-color: oklch(55% 0.2 150);
  border-color: oklch(55% 0.2 150);
}

/* Pill-shaped inputs */
.Input--pill {
  border-radius: 999px;
  padding-left: var(--space-4);
  padding-right: var(--space-4);
}

/* Custom error color */
.Input--error {
  border-color: oklch(55% 0.25 0); /* Deeper red */
}

API Reference

Form Field Classes

Class Description
.FormField Container for label, input, and helper text
.FormField-label Label styling for form fields
.FormField-label--required Adds asterisk to indicate required field
.FormField-helper Helper text below input
.FormField-message Validation message container
.FormField-message--error Error message styling (red)
.FormField-message--success Success message styling (green)
.FormField-footer Container for helper + count
.FormField-count Character count display

Input Classes

Class Description
.Input Base input styling (required)
.Input--small Small input size
.Input--large Large input size
.Input--error Error state (red border)
.Input--success Success state (green border)
.Input--withIcon Padding for leading icon
.Input--withIconTrailing Padding for trailing icon
.Input--grouped For input + button combos
.Input-wrapper Container for input with icon
.Input-icon Icon inside input wrapper
.Input-icon--trailing Position icon on right
.Input-toggle Password visibility toggle button
.Input-group Container for input + button

Textarea Classes

Class Description
.Textarea Base textarea styling
.Textarea--error Error state (red border)

Select Classes

Class Description
.Select Base select/dropdown styling

Checkbox Classes

Class Description
.Checkbox Container for custom checkbox
.Checkbox-input Hidden native checkbox
.Checkbox-box Custom checkbox visual
.Checkbox-box--indeterminate Indeterminate state styling
.Checkbox-label Label text for checkbox
.Checkbox--disabled Disabled state
.CheckboxGroup Container for multiple checkboxes

Radio Classes

Class Description
.Radio Container for custom radio
.Radio-input Hidden native radio
.Radio-circle Custom radio visual
.Radio-label Label text for radio
.RadioGroup Container for radio group
.RadioCardGroup Container for radio cards
.RadioCard Rich radio option with description
.RadioCard--selected Selected radio card state

Switch Classes

Class Description
.Switch Container for toggle switch
.Switch-input Hidden native checkbox
.Switch-track Switch track and thumb
.Switch-label Label text for switch
.Switch--block Full-width switch with border
.Switch--small Small switch size
.Switch--large Large switch size
.Switch--disabled Disabled state
.Switch-content Container for label + description
.Switch-description Secondary text under label

File Upload Classes

Class Description
.FileInput File upload container
.FileInput-input Hidden native file input
.FileInput-label Styled drop zone label
.FileInput-icon Upload icon
.FileInput-text Main drop zone text
.FileInput-hint File type/size hint
.FileInput--with-preview Layout for preview thumbnail
.FileInput--multiple Multiple file upload variant
.FileInput-list Container for uploaded file list
.FileInput-item Individual file item

Layout Classes

Class Description
.FormInline Horizontal form layout
.FormRow Two-column grid row
.FormFieldset Fieldset without default styling
.FormFieldset-legend Fieldset legend styling

CSS Reference

/* Form Field */
.FormField {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.FormField-label {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--fg);
}

.FormField-label--required::after {
  content: " *";
  color: oklch(55% 0.2 25);
}

.FormField-helper {
  font-size: 0.8rem;
  color: var(--fg-3);
}

.FormField-message {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 0.8rem;
}

.FormField-message--error {
  color: oklch(55% 0.2 25);
}

.FormField-message--success {
  color: oklch(55% 0.15 150);
}

.FormField-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.FormField-count {
  font-size: 0.8rem;
  color: var(--fg-3);
}

/* Input */
.Input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
  border: 1px solid var(--bd-s);
  border-radius: var(--r-s);
  background: var(--bg);
  color: var(--fg);
  transition: border-color 0.15s, box-shadow 0.15s;
}

.Input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px oklch(60% 0.15 250 / 0.15);
}

.Input::placeholder {
  color: var(--fg-3);
}

.Input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--bg-s);
}

/* Input Sizes */
.Input--small {
  padding: var(--space-1) var(--space-2);
  font-size: 0.8rem;
}

.Input--large {
  padding: var(--space-3) var(--space-4);
  font-size: 1rem;
}

/* Input States */
.Input--error {
  border-color: oklch(55% 0.2 25);
}

.Input--error:focus {
  box-shadow: 0 0 0 2px oklch(55% 0.2 25 / 0.15);
}

.Input--success {
  border-color: oklch(55% 0.15 150);
}

.Input--success:focus {
  box-shadow: 0 0 0 2px oklch(55% 0.15 150 / 0.15);
}

/* Input with Icons */
.Input-wrapper {
  position: relative;
  display: flex;
  align-items: center;
}

.Input-icon {
  position: absolute;
  left: var(--space-3);
  color: var(--fg-3);
  pointer-events: none;
}

.Input-icon--trailing {
  left: auto;
  right: var(--space-3);
}

.Input--withIcon {
  padding-left: calc(var(--space-3) + 1.25rem + var(--space-2));
}

.Input--withIconTrailing {
  padding-right: calc(var(--space-3) + 1.25rem + var(--space-2));
}

.Input-toggle {
  position: absolute;
  right: var(--space-2);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg-3);
  padding: var(--space-1);
}

/* Input Group */
.Input-group {
  display: flex;
}

.Input--grouped {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  border-right: none;
}

.Input-group .Button {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

/* Textarea */
.Textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
  border: 1px solid var(--bd-s);
  border-radius: var(--r-s);
  background: var(--bg);
  color: var(--fg);
  resize: vertical;
  min-height: 80px;
  font-family: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.Textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px oklch(60% 0.15 250 / 0.15);
}

/* Select */
.Select {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
  border: 1px solid var(--bd-s);
  border-radius: var(--r-s);
  background: var(--bg);
  color: var(--fg);
  appearance: none;
  background-image: url("data:image/svg+xml,...");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  cursor: pointer;
}

/* Checkbox */
.Checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}

.Checkbox-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.Checkbox-box {
  width: 18px;
  height: 18px;
  border: 2px solid var(--bd-s);
  border-radius: var(--r-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s, border-color 0.15s;
  flex-shrink: 0;
}

.Checkbox-input:checked + .Checkbox-box {
  background-color: var(--accent);
  border-color: var(--accent);
}

.Checkbox-input:focus-visible + .Checkbox-box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.Checkbox--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.Checkbox-label {
  font-size: 0.9rem;
}

/* Radio */
.Radio {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}

.Radio-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.Radio-circle {
  width: 18px;
  height: 18px;
  border: 2px solid var(--bd-s);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s;
  flex-shrink: 0;
}

.Radio-input:checked + .Radio-circle {
  border-color: var(--accent);
}

.Radio-input:checked + .Radio-circle::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.RadioGroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Radio Card */
.RadioCardGroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.RadioCard {
  display: flex;
  padding: var(--space-4);
  border: 1px solid var(--bd);
  border-radius: var(--r-m);
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.RadioCard--selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* Switch */
.Switch {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
}

.Switch-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.Switch-track {
  width: 40px;
  height: 22px;
  background: var(--bg-s);
  border: 1px solid var(--bd-s);
  border-radius: 11px;
  position: relative;
  transition: background-color 0.2s, border-color 0.2s;
  flex-shrink: 0;
}

.Switch-track::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: white;
  top: 2px;
  left: 2px;
  transition: transform 0.2s;
}

.Switch-input:checked + .Switch-track {
  background: var(--accent);
  border-color: var(--accent);
}

.Switch-input:checked + .Switch-track::after {
  transform: translateX(18px);
}

.Switch--small .Switch-track { width: 32px; height: 18px; }
.Switch--large .Switch-track { width: 48px; height: 26px; }

.Switch--disabled { opacity: 0.5; cursor: not-allowed; }

.Switch--block {
  display: flex;
  justify-content: space-between;
  padding: var(--space-3);
  border: 1px solid var(--bd);
  border-radius: var(--r-m);
}

.Switch-content { display: flex; flex-direction: column; gap: var(--space-1); }
.Switch-description { font-size: 0.8rem; color: var(--fg-3); }

/* File Input */
.FileInput {
  position: relative;
}

.FileInput-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.FileInput-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6);
  border: 2px dashed var(--bd);
  border-radius: var(--r-m);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s, background 0.15s;
}

.FileInput-label:hover {
  border-color: var(--accent);
  background: oklch(60% 0.15 250 / 0.05);
}

.FileInput-icon { font-size: 1.5rem; color: var(--fg-3); }
.FileInput-text { font-weight: 500; }
.FileInput-hint { font-size: 0.8rem; color: var(--fg-3); }

.FileInput-list { display: flex; flex-direction: column; gap: var(--space-2); }
.FileInput-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--bd);
  border-radius: var(--r-s);
}

/* Form Layouts */
.FormInline {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
}

.FormRow {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.FormFieldset {
  border: none;
  padding: 0;
  margin: 0;
}

.FormFieldset-legend {
  font-weight: 600;
  margin-bottom: var(--space-3);
}

Accessibility

Labels

Always associate labels with inputs using for/id or by wrapping.

<!-- Method 1: for/id association -->
<label for="email-field">Email</label>
<input type="email" id="email-field" class="Input">

<!-- Method 2: Wrap input in label -->
<label class="Checkbox">
    <input type="checkbox">
    <span class="Checkbox-label">Accept terms</span>
</label>

Error Messages

Connect error messages to inputs with aria-describedby.

<input 
    type="email" 
    id="email" 
    class="Input Input--error" 
    aria-invalid="true"
    aria-describedby="email-error"
>
<span id="email-error" class="FormField-message FormField-message--error">
    Please enter a valid email
</span>

Required Fields

Mark required fields programmatically.

<label for="name">
    Name <span aria-hidden="true">*</span>
</label>
<input type="text" id="name" required aria-required="true">

Fieldsets for Groups

Use fieldsets to group related fields.

<fieldset>
    <legend>Shipping Address</legend>
    <!-- Address fields -->
</fieldset>

Keyboard Support

Key Action
Tab Move focus to next field
Shift + Tab Move focus to previous field
Space Toggle checkbox/switch, select radio
Arrow keys Navigate between radio options
Enter Submit form (when on button)

Best Practices

Do

  • Use clear, descriptive labels — “Email address” not “Input 1”
  • Group related fields — Use fieldsets for address, payment info
  • Show validation inline — Error messages next to the field
  • Indicate required fields — Asterisk or “(required)” text
  • Provide helper text — Format hints, password requirements
  • Use appropriate input typestype="email", type="tel", etc.
  • Size touch targets — Minimum 44px for mobile

Don’t

  • Use placeholder as label — Placeholders disappear on focus
  • Disable submit without explanation — Show why it’s disabled
  • Validate on every keystroke — Wait for blur or submit
  • Use only color for errors — Add icons and text
  • Reset forms on error — Preserve user input
  • Hide password requirements — Show them upfront