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.
<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.
<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.
<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.
<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.
<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.
<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
<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
<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
<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
<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
<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 types —
type="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