form
Container for interactive form controls that collects and submits user data. The form element provides structure, validation, and submission handling.
When to Use
- Collecting user input (contact forms, login, registration)
- Search functionality
- Any interaction that submits data to a server
- Client-side form validation with HTML5 attributes
When Not to Use
- For layout or grouping unrelated content (use
<section>or<fieldset>) - For links styled as buttons (use
<a>directly) - For toggling UI state without server submission (use JavaScript event handlers)
Basic Usage
A minimal form with form.stacked layout, <form-field> wrappers, and a <footer class="actions"> for buttons.
<form action="/api/submit" method="POST" class="stacked"> <form-field> <label for="name">Name</label> <input type="text" id="name" name="name" required /> </form-field> <form-field> <label for="email">Email</label> <input type="email" id="email" name="email" required /> </form-field> <footer class="actions end"> <button type="submit">Submit</button> </footer></form>
Layout Variants
VB provides three layout classes on <form> that handle spacing and direction without extra wrappers.
form.stacked
Fields arranged vertically with consistent spacing. Best for general-purpose forms.
form.inline
Fields arranged horizontally with wrapping. Best for search bars and compact forms.
form.grid
Labels on the left, inputs on the right in a two-column grid. Best for data entry forms.
<!-- Vertical stacked layout --><form class="stacked"> <form-field> <label for="name">Name</label> <input type="text" id="name" /> </form-field> <footer class="actions end"> <button type="submit">Send</button> </footer></form> <!-- Horizontal inline layout --><form class="inline"> <input type="search" placeholder="Search..." /> <button type="submit">Search</button></form> <!-- Two-column grid layout --><form class="grid"> <label for="first">First Name</label> <input type="text" id="first" /> <label for="last">Last Name</label> <input type="text" id="last" /></form>
| Class | Display | Best for |
|---|---|---|
form.stacked |
Vertical flex column | General-purpose forms, contact forms |
form.inline |
Horizontal flex with wrap | Search bars, filters, compact forms |
form.grid |
Two-column grid | Data entry, settings panels |
Form Attributes
| Attribute | Purpose | Example |
|---|---|---|
action |
URL to submit data to | action="/api/contact" |
method |
HTTP method (GET or POST) | method="POST" |
enctype |
Encoding type for POST data | enctype="multipart/form-data" |
novalidate |
Disable browser validation | novalidate |
autocomplete |
Enable/disable autofill | autocomplete="on" |
name |
Form name for scripting | name="contact-form" |
Using form-field
The <form-field> web component wraps a label, input, and optional help/error text into a single accessible unit. It handles aria-describedby wiring automatically and adds required indicators.
<form class="stacked"> <form-field> <label for="email">Email</label> <input type="email" id="email" required /> <output for="email" aria-live="polite"> We'll never share your email. </output> </form-field></form>
Form Actions
Use <footer class="actions"> for the submit/cancel button row at the bottom of a form. The .end modifier right-aligns buttons; .between pushes groups apart.
<!-- End-aligned (most common) --><form class="stacked"> ...fields... <footer class="actions end"> <button type="button" class="secondary">Cancel</button> <button type="submit">Submit</button> </footer></form> <!-- Space between (delete + save) --><form class="stacked"> ...fields... <footer class="actions between"> <button type="button" class="ghost">Delete</button> <span> <button type="button" class="secondary">Cancel</button> <button type="submit">Save</button> </span> </footer></form>
| Class | Alignment |
|---|---|
.actions |
Start-aligned (default) |
.actions.end |
End-aligned |
.actions.between |
Space between groups |
Help and Error Text
Inside a <form-field>, add a <span class="help"> for hints or a <span class="error"> for validation messages. Mark invalid inputs with aria-invalid="true".
<form-field> <label for="password">Password</label> <input type="password" id="password" /> <span class="help">At least 8 characters.</span></form-field> <form-field> <label for="email">Email</label> <input type="email" id="email" aria-invalid="true" /> <span class="error">Invalid email address.</span></form-field>
Complete Contact Form
A full example combining form.stacked, <form-field>, <fieldset>, and <footer class="actions">.
Conditional Fields (data-show-when)
Use data-show-when or data-hide-when on any element inside a form to conditionally display sections based on another field’s value. Hidden elements get hidden and inert attributes, preventing them from blocking validation.
Show When
Show a section when a field has a specific value.
<select name="account-type"> <option value="personal">Personal</option> <option value="business">Business</option></select> <fieldset data-show-when="account-type=business"> <legend>Business Details</legend> <input name="company" placeholder="Company name"></fieldset>
Hide When
Hide a section when a condition is met — the inverse of data-show-when.
<label> <input type="checkbox" name="gift" value="yes"> This is a gift</label> <form-field data-hide-when="gift=yes"> <label for="receipt">Send receipt to</label> <input type="email" id="receipt" name="receipt"></form-field>
| Attribute | Format | Description |
|---|---|---|
data-show-when |
name=value |
Show when the named field equals the value. |
data-hide-when |
name=value |
Hide when the named field equals the value. |
Works with <select>, <input type="radio">, <input type="checkbox">, and text inputs. The name refers to the name attribute of the controlling field within the same <form>.
Form Autosave (data-autosave)
Add data-autosave="key" to a <form> to automatically save its data to localStorage as the user types. On page reload, the draft is restored and a “Draft restored” toast appears. Drafts are cleared on submit or reset and expire after 24 hours.
<form data-autosave="contact-form"> <input name="name" placeholder="Name"> <textarea name="message" placeholder="Message"></textarea> <button type="submit">Send</button></form>
| Attribute | Description |
|---|---|
data-autosave |
Storage key for this form’s draft. Must be unique per form. |
Behavior
- Save: Debounced (500ms) on every
inputandchangeevent - Restore: On page load if a non-expired draft exists
- Clear: On form
submitorreset - Skip: Password and file inputs are never saved
- Expire: Drafts older than 24 hours are discarded
Accessibility Notes
- Label all inputs: Every form control needs an associated label
- Use fieldsets: Group related fields with fieldset and legend
- Error identification: Use
aria-invalidandaria-describedbyfor errors - Autocomplete: Add appropriate
autocompleteattributes for autofill - Focus management: Use
autofocuson the first field (one per page) - Submit button: Always include a visible submit button, not just Enter key handling
- Use form-field: The
<form-field>component handlesaria-describedbywiring automatically
CSS Reference
form { display: block;} /* Vertical stacked layout */form.stacked { display: flex; flex-direction: column; gap: var(--size-m);} /* Horizontal inline layout */form.inline { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--size-s);} /* Two-column grid layout */form.grid { display: grid; grid-template-columns: minmax(100px, auto) 1fr; gap: var(--size-s) var(--size-m); align-items: center;} /* Legacy group wrapper (prefer form-field) */.group { display: flex; flex-direction: column; gap: var(--size-2xs);} .group.horizontal { flex-direction: row; align-items: center; gap: var(--size-s);} /* Form actions */.actions { display: flex; gap: var(--size-s); margin-block-start: var(--size-m);} .actions.end { justify-content: flex-end; }.actions.between { justify-content: space-between; } /* Help and error text */.help { font-size: var(--font-size-sm); color: var(--color-text-muted);} .error { font-size: var(--font-size-sm); color: oklch(55% 0.2 25);}
Related
- input - Text inputs, checkboxes, radios, and more
- button - Form submission and reset buttons
- select - Dropdown selection menus
- textarea - Multi-line text input
- fieldset - Group related form fields
- label - Accessible input labels
- legend - Caption for fieldset groups
- output - Calculation result or action output
- form-field - Enhanced form field wrapper with validation
- datalist - Autocomplete suggestions for inputs
- dialog - Modal forms with
method="dialog"