Forms
Structured input layouts with built-in validation.
Setup
Form styles are included in Manifest CSS or a standalone stylesheet, both referencing theme variables.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.css" />
Default
The <form> element arranges its contents in a column with gaps.
<form>
<label for="input1">Input 1</label>
<input id="input1" placeholder="Insert" />
<label for="input2">Input 2</label>
<input id="input2" placeholder="Insert" />
</form>
Place form elements inside a label for enhanced default styling, and to reduce code required. Label text can be standalone or in a <span>.
<form>
<label>
Input 1
<input placeholder="Insert" />
</label>
<label>
<span>Input 2</span>
<input placeholder="Insert" />
</label>
</form>
This works for all types of form elements.
<form>
<label>
Button label
<button>Button</button>
</label>
<label>
Text input label
<input placeholder="Input" />
</label>
<!-- Search and file inputs require an external label for text, and can be visually grouped in a fieldset wrapper -->
<fieldset>
<label for="search">Search input label</label>
<label>
<i x-icon="lucide:search"></i>
<input id="search" type="search" />
</label>
</fieldset>
<fieldset>
<label for="file">File input label</label>
<label>
<i x-icon="lucide:upload"></i>
Upload
<input id="file" type="file" />
</label>
</fieldset>
<label>
Select label
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</label>
<label>
Textarea label
<textarea placeholder="Insert"></textarea>
</label>
<label>
<input type="checkbox" role="switch" />
Switch label
</label>
<!-- Visually group checkbox and radio sets with a fieldset wrapper -->
<fieldset>
<label>
<input type="checkbox" />
Checkbox label
</label>
<label>
<input type="checkbox" />
Checkbox label
</label>
</fieldset>
<fieldset>
<label>
<input type="radio" name="radio-set" />
Radio label
</label>
<label>
<input type="radio" name="radio-set" />
Radio label
</label>
</fieldset>
</form>
Inline Labels
To horizontally inline the label text with its form element, place the text in a <data> element, which Manifest uses as a CSS hook. <data> elements are semantically neutral, equivalent to a <span>.
<form>
<label>
<data>Button label</data>
<button>Button</button>
</label>
<label>
<data>Text input label</data>
<input placeholder="Input" />
</label>
<!-- For search and file inputs, use a div with the `label` utility class for the inner wrapper — this avoids <label> element nesting -->
<label>
<data>Search input label</data>
<div class="label">
<i x-icon="lucide:search"></i>
<input id="search" type="search" />
</div>
</label>
<label>
<data>File input label</data>
<div class="label">
<i x-icon="lucide:upload"></i>
Upload
<input id="file" type="file" />
</div>
</label>
<label>
<data>Select label</data>
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</label>
<label>
<data>Textarea label</data>
<textarea placeholder="Insert"></textarea>
</label>
<label>
<data>Switch label</data>
<input type="checkbox" role="switch" />
</label>
<fieldset>
<label>
<data>Checkbox label</data>
<input type="checkbox" />
</label>
<label>
<data>Checkbox label</data>
<input type="checkbox" />
</label>
</fieldset>
<fieldset>
<label>
<data>Radio label</data>
<input type="radio" name="radio-set" />
</label>
<label>
<data>Radio label</data>
<input type="radio" name="radio-set" />
</label>
</fieldset>
</form>
Fieldset Legends
Add a <legend> element to a <fieldset> with checkboxes or radios, to create a bordered container with a small title.
<fieldset>
<legend>Preferences</legend>
<label>
<input type="checkbox" />
<data>Email notifications</data>
</label>
<label>
<input type="checkbox" />
<data>SMS notifications</data>
</label>
</fieldset>
Group Wrappers
Buttons, inputs, and selects can be arranged horizontally flush inside a wrapper with the role="group" attribute. Elements are connected seamlessly with shared borders.
<div role="group">
<select>
<option>Category</option>
<option>Technology</option>
<option>Design</option>
</select>
<input placeholder="Filter" />
<button>Apply</button>
</div>
The even utility class makes all form elements an equal width.
<div role="group" class="even">
<select>
<option>Category</option>
<option>Technology</option>
<option>Design</option>
</select>
<input placeholder="Filter" />
<button>Apply</button>
</div>
A wrapper with role="tablist" instead becomes a tab bar. Mark the active button (or <a role="button">) with the selected class — or aria-selected="true" / aria-current — and a highlighted background slides behind the active item.
<div role="tablist" x-data="{ tab: 1 }">
<button :class="tab === 1 && 'selected'" @click="tab = 1">Item 1</button>
<button :class="tab === 2 && 'selected'" @click="tab = 2">Item 2</button>
<button :class="tab === 3 && 'selected'" @click="tab = 3">Item 3</button>
</div>
Tab buttons sync aria-selected automatically, so adding role="tablist" to a wrapper of x-tab buttons gets this styling with no further markup.
Button utility classes like transparent, outlined, sm, and lg, also apply at the wrapper level, styling the whole control as one.
Remove Buttons
A button named remove becomes a floating dismiss affordance for whatever contains it — a chip, an avatar, a pending chat attachment. The complete markup is one attribute pair; everything else is automatic.
<span class="badge">
Design
<button type="button" name="remove" aria-label="Remove Design"></button>
</span>
The parent element anchors the button automatically (it's given position: relative), the button floats over the parent's top end corner above neighbouring content, and it stays hidden until the parent is hovered or holds focus — all in CSS. The ✕ glyph comes from the theme's --icon-dismiss variable, so no icon markup is needed; the aria-label is required since the button has no visible text.
A few notes:
- The parent can be anything, anywhere —
nameis a native button attribute and carries no behavior outside a form. - In a server-rendered form,
<button type="submit" name="remove" value="42">submits the pair as its payload — the styling hook and the delete request are the same attribute. - Touch devices have no hover: the button also reveals while anything inside the parent holds focus, but for touch-first UIs consider overriding
opacityto keep it always visible.
Validation
Native HTML5 attributes like required, type="email", pattern, minlength, and maxlength trigger the browser's built-in form validation on submit. Custom CSS or Tailwind pseudo-class variants can be used to style valid and invalid states without Javascript. Block the form's own validation messages with novalidate.
<form class="group">
<label>
Email
<input type="email" required
class="user-invalid:negative user-valid:positive" />
</label>
<label>
Subject
<input type="text" required minlength="8"
class="user-invalid:negative user-valid:positive" />
</label>
<button class="group-invalid:disabled accent">Submit</button>
</form>
Common Tailwind variants:
| Variant | Triggers when |
|---|---|
invalid: |
The field's value fails its native validation rules |
valid: |
The field's value passes its native validation rules |
user-invalid: |
Same as invalid: but only after the user has interacted with the field |
user-valid: |
Same as valid: but only after the user has interacted with the field |
peer-invalid: |
A .peer sibling fails its native validation rules |
peer-user-invalid: |
A .peer sibling fails validation only after user interaction |
group-invalid: |
A .group parent contains one or more invalid fields |
group-user-invalid: |
A .group parent contains one or more user-invalid fields (after user interaction) |
required: |
The field has the required attribute |
placeholder-shown: |
The field is empty and showing its placeholder |
focus: |
The field is currently focused |
user-invalid: and user-valid: are better suited than the unprefixed invalid: and valid: for most cases. They wait until the user has actually engaged with the field, avoiding the awkward "everything is red on page load" effect.
Styles
Forms inherit styling from their child elements (buttons, inputs, selects) and reference theme variables. The form, fieldset, label, and group elements layer in a small amount of layout structure on top.
| Selector | Purpose |
|---|---|
form |
Default column layout with vertical gaps between children |
fieldset |
Bordered grouping with column layout |
legend |
Fieldset title styling |
label |
Stacks a label above its form control |
label > data |
Inlines the label text horizontally with the control |
[role="group"] |
Horizontal row of form elements with shared borders |
[role="group"].even |
Equal-width siblings inside a group |
[role="tablist"] |
Tab bar with a background that slides behind the selected item |
button[name="remove"] |
Floating dismiss affordance anchored to its parent element |
Customization
Target any of the selectors above with custom CSS.
form {
gap: 1rem;
padding: 1rem;
background: var(--color-surface-2);
border-radius: var(--radius);
}
Article does not exist
There is no documentation at this path.