<form class="w-full max-w-md space-y-6">
<fieldset class="fieldset">
<legend>Payment Method</legend>
<p>All transactions are secure and encrypted</p>
<div role="group" class="field">
<label for="card-name">Name on Card</label>
<input id="card-name" type="text" placeholder="Evil Rabbit" required>
</div>
<div role="group" class="field">
<label for="card-number">Card Number</label>
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required>
<p id="card-number-desc">Enter your 16-digit card number</p>
</div>
<div class="grid grid-cols-3 gap-4">
<div role="group" class="field">
<label for="exp-month">Month</label>
<select id="exp-month" class="select w-full">
<option value="">MM</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
</div>
<div role="group" class="field">
<label for="exp-year">Year</label>
<select id="exp-year" class="select w-full">
<option value="">YYYY</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
<option value="2026">2026</option>
<option value="2027">2027</option>
<option value="2028">2028</option>
<option value="2029">2029</option>
</select>
</div>
<div role="group" class="field">
<label for="cvv">CVV</label>
<input id="cvv" type="text" placeholder="123" required>
</div>
</div>
</fieldset>
<hr role="separator" class="my-6 border-border">
<fieldset class="fieldset">
<legend>Billing Address</legend>
<p>The billing address associated with your payment method</p>
<div role="group" class="field">
<label for="same-as-shipping" class="gap-3">
<input type="checkbox" id="same-as-shipping" checked>
Same as shipping address
</label>
</div>
</fieldset>
<fieldset class="fieldset">
<div role="group" class="field">
<label for="comments">Comments</label>
<textarea id="comments" placeholder="Add any additional comments" rows="3"></textarea>
</div>
</fieldset>
<div class="flex gap-3">
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn-outline">Cancel</button>
</div>
</form>
Usage
Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:
<div role="group" class="field">for individual field containers (label, input, helper text, errors)<fieldset class="fieldset">for grouping multiple related fields together
Elements inside these containers are styled automatically based on their purpose. Use aria-describedby to connect helper text and errors to inputs for accessibility.
Fields have the following HTML structure:
<div role="group" class="field">- Field container. Add
data-orientation="horizontal"to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.<label>- Label for the input.
<input>,<select>or<textarea>- Form input.
<p>Optional- Helper text. Use
aria-describedbyon the input to reference this element'sid. <p role="alert">Optional- Error message. Use
aria-describedbyon the input andaria-invalid="true"to reference this element'sid. <section>Optional- Wraps label and description when the label sits beside the input (e.g.
data-orientation="horizontal"). Not required if you have no description.
Fieldsets have the following HTML structure:
<fieldset class="fieldset">- Container for grouping related fields.
<legend>- Heading for the fieldset.
<p>Optional- Description text. When placed directly after
<legend>, automatically styled as muted text. <div role="group" class="field">Optional- Individual field containers (see structure above). You can include multiple
.fieldelements, or place form controls directly in the fieldset.
Examples
Input
Choose a unique username for your account.
Must be at least 8 characters long.
<div class="grid gap-6">
<div role="group" class="field">
<label for="username">Username</label>
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc">
<p id="username-desc">Choose a unique username for your account.</p>
</div>
<div role="group" class="field">
<label for="password">Password</label>
<p id="password-desc">Must be at least 8 characters long.</p>
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc">
</div>
</div>
Textarea
Share your thoughts about our service.
<div role="group" class="field">
<label for="bio">Feedback</label>
<textarea id="bio" rows="4" placeholder="Your feedback helps us improve..." aria-describedby="bio-desc"></textarea>
<p id="bio-desc">Share your thoughts about our service.</p>
</div>
Select
Select your department or area of work.
<div role="group" class="field">
<label for="country">Department</label>
<select id="country" class="select w-full" aria-describedby="country-desc">
<option value="">Choose department</option>
<option value="engineering">Engineering</option>
<option value="design">Design</option>
<option value="marketing">Marketing</option>
<option value="sales">Sales</option>
<option value="support">Customer Support</option>
<option value="hr">Human Resources</option>
<option value="finance">Finance</option>
<option value="operations">Operations</option>
</select>
<p id="country-desc">Select your department or area of work.</p>
</div>
Slider
Set your budget: $150
<div role="group" class="field">
<label for="price-range">Price</label>
<p id="price-range-desc">Set your budget: $<span id="price-range-value">150</span></p>
<input id="price-range" type="range" min="0" max="500" value="150">
</div>
<script>
(() => {
const sliders = document.querySelectorAll('input[type="range"]');
if (!sliders) return;
const updateSlider = (el) => {
const min = parseFloat(el.min || 0);
const max = parseFloat(el.max || 100);
const value = parseFloat(el.value);
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
el.style.setProperty('--slider-value', `${percent}%`);
};
sliders.forEach(slider => {
updateSlider(slider);
slider.addEventListener('input', (event) => updateSlider(event.target));
});
const priceSlider = document.getElementById('price-range');
const priceValue = document.getElementById('price-range-value');
priceSlider.addEventListener('input', (event) => priceValue.innerHTML = event.target.value);
})();
</script>
Fieldset
Group related fields with <fieldset class="fieldset"> and <legend>.
<form class="w-full max-w-md">
<fieldset class="fieldset">
<legend>Profile</legend>
<p>This information will be displayed on your profile</p>
<div role="group" class="field">
<label for="full-name">Full name</label>
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc">
<p id="name-desc">Your first and last name</p>
</div>
<div role="group" class="field">
<label for="display-name">Username</label>
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error">
<p id="username-error" role="alert">Username is already taken</p>
</div>
<div class="flex items-center gap-3">
<input type="checkbox" id="newsletter">
<label for="newsletter" class="font-normal">Subscribe to the newsletter</label>
</div>
</fieldset>
<button type="submit" class="btn mt-4">Save changes</button>
</form>
Checkbox
<fieldset class="fieldset">
<legend>Show these items on the desktop</legend>
<p>Select the items you want to show on the desktop.</p>
<div class="flex flex-col gap-3">
<div class="field">
<label class="gap-3">
<input type="checkbox">
Hard disks
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
External disks
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
CDs, DVDs and iPods
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
Connected servers
</label>
</div>
</div>
<hr role="separator">
<div class="field" data-orientation="horizontal">
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
<section>
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
</section>
</div>
</fieldset>
Radio Group
<fieldset class="fieldset">
<legend>Subscription plan</legend>
<p>Yearly and lifetime plans offer significant savings.</p>
<div role="radiogroup" class="grid gap-3">
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan" checked>
Monthly ($9.99/month)
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Yearly ($99.99/year)
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Lifetime ($299.99)
</label>
</div>
</div>
</fieldset>
Switch
Set data-orientation="horizontal" on .field to align labels and controls side-by-side.
Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.
<div role="group" class="field" data-orientation="horizontal">
<section>
<label for="multi-factor-authentication">Multi-factor authentication</label>
<p>Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.</p>
</section>
<input id="multi-factor-authentication" type="checkbox" role="switch">
</div>
Choice Card
<fieldset class="fieldset">
<legend class="text-sm">Compute Environment</legend>
<p>Select the compute environment for your cluster.</p>
<div role="group" class="field">
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<section>
<h3>Kubernetes</h3>
<p>Run GPU workloads on a K8s configured cluster.</p>
</section>
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked>
</label>
</div>
<div role="group" class="field">
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<section>
<h3>Virtual Machine</h3>
<p>Access a VM configured cluster to run GPU workloads.</p>
</section>
<input id="choice-card" type="radio" name="choice-card" class="self-start">
</label>
</div>
</fieldset>
Responsive Layout
For responsive layouts, use standard Tailwind responsive classes directly on .field. This example stacks vertically on mobile and switches to horizontal at the md breakpoint.
<fieldset class="fieldset">
<legend>Profile</legend>
<p>Keep your profile details up to date.</p>
<hr role="separator">
<div class="flex flex-col gap-7">
<div role="group" class="field flex-col md:flex-row md:items-center">
<section class="md:flex-auto">
<label for="profile-name">Name</label>
<p id="profile-name-desc">Provide your full name for identification.</p>
</section>
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80">
</div>
<hr role="separator">
<div role="group" class="field flex-col md:flex-row md:items-start">
<section class="md:flex-auto">
<label for="profile-bio">Message</label>
<p id="profile-bio-desc">You can write your message here. Keep it short, preferably under 100 characters.</p>
</section>
<textarea id="profile-bio" rows="3" placeholder="Hello, world!" aria-describedby="profile-bio-desc" class="md:w-auto md:min-w-80"></textarea>
</div>
<hr role="separator">
<div role="group" class="field flex-col md:flex-row md:items-center">
<button type="submit" class="btn md:w-auto">Submit</button>
<button type="button" class="btn-outline md:w-auto">Cancel</button>
</div>
</div>
</fieldset>