checked, disabled, required, invalid
Form State Variants
Style inputs based on their validation and interaction state declaratively, with no JavaScript needed.
What you'll learn
- Use checked, disabled, required, and read-only
- Use invalid, valid, and the friendlier user-invalid
- Build accessible field states with sensible base styling
The browser already tracks whether a field is checked, disabled, required, or valid. Form variants let you style those states without wiring up change handlers.
Interaction States
disabled:, checked:, and read-only: are the everyday ones:
<input type="checkbox"
class="accent-blue-600 checked:ring-2 checked:ring-blue-500">
<button class="rounded bg-blue-600 px-4 py-2 text-white
disabled:cursor-not-allowed disabled:opacity-50"
disabled>
Save
</button> disabled:opacity-50 and disabled:cursor-not-allowed communicate an inert control clearly, and they update automatically when the disabled attribute changes.
Required and Validation
Mark required fields and react to validity. required: on the label can append an asterisk:
<label class="required:after:content-['*'] required:after:text-red-500">
Email
</label>
<input type="email" required
class="border invalid:border-red-500 valid:border-green-500"> Plain invalid: styles a field red the instant it loads while still empty, which feels hostile. Prefer user-invalid:, which only applies after the user has interacted with and left the field:
<input type="email" required
class="border user-invalid:border-red-500
user-invalid:ring-1 user-invalid:ring-red-500"> A Sensible Baseline
Native form controls render inconsistently across browsers. The official forms plugin gives them a clean, restyle-friendly base so your variants have something predictable to build on:
@import "tailwindcss";
@plugin "@tailwindcss/forms"; With that in place, checked:, disabled:, and user-invalid: produce consistent, accessible field states across the board.