Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Container for interactive form controls that collects and submits user data. The form element provides structure, validation, and submission handling.
<section> or <fieldset>)<a> directly)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>
VB provides three layout classes on <form> that handle spacing and direction without extra wrappers.
Fields arranged vertically with consistent spacing. Best for general-purpose forms.
Fields arranged horizontally with wrapping. Best for search bars and compact forms.
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 |
| 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" |
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>
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 |
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>
A full example combining form.stacked, <form-field>, <fieldset>, and <footer class="actions">.
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 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 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>.
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. |
input and change eventsubmit or resetaria-invalid and aria-describedby for errorsautocomplete attributes for autofillautofocus on the first field (one per page)<form-field> component handles aria-describedby wiring automaticallyform { 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);}
method="dialog"