Use of `has` Variant
Use `has-{modifier}` to style an element based on the state of its descendants.
TailwindCSS’s has-{modifier}
variant is a game-changer for scenarios where you want to apply styles to a parent element based on the state of its child elements. Here are some practical use cases demonstrating how to leverage this feature effectively.
Disabled input
Make a parent container visually indicate when its child input field is disabled.
<div class="space-y-2.5 has-disabled:opacity-50 has-disabled:pointer-events-none">
<label
for="email"
class="text-gray-950 text-sm dark:text-white"
>
Your Email
</label>
<input
disabled
type="email"
id="email"
class="block outline-hidden w-full px-3 h-9 text-sm rounded-lg border shadow-sm shadow-gray-950/5 focus:ring-2 focus:ring-primary-600 dark:bg-gray-800/25 dark:border-gray-800"
placeholder="Enter your email"
/>
</div>
Input validation
Display an error message dynamically when an invalid value is detected.
<div class="space-y-2.5 [--error-display:none] has-[:user-invalid]:[--error-display:block]">
<label
for="email"
class="text-gray-950 text-sm dark:text-white"
>
Your Email
</label>
<input
type="email"
id="email"
class="block outline-hidden w-full px-3 h-9 text-sm rounded-lg border shadow-sm shadow-gray-950/5 focus:ring-2 focus:ring-primary-600 dark:bg-gray-800/25 dark:border-gray-800"
placeholder="Enter your email"
/>
<div class="[display:var(--error-display)] text-sm text-danger-600 dark:text-danger-400">Please enter a valid email</div>
</div>
Radio Cards
Highlight a card-like UI when its associated radio input is checked.
<form>
<label class="block space-y-2.5 px-4 rounded-2xl border border-gray-200 shadow-sm shadow-gray-950/5 py-3 hover:bg-gray-50 [--icon-display:none] has-checked:border-transparent has-checked:[--icon-display:block] has-checked:outline has-checked:outline-2 has-checked:outline-indigo-600 dark:hover:bg-gray-800/50 dark:border-gray-800" for="personal2">
<input type="radio" id="personal2" name="plan" hidden />
<div class="flex justify-between">
<p class="text-sm text-gray-950 font-medium dark:text-white">4-Core CPU</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5 text-primary-600 [display:var(--icon-display)]">
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
</svg>
</div>
<p class="mt-1 text-gray-500 text-sm">For personal projects</p>
<p class="mt-4 text-gray-600 text-sm dark:text-gray-400">$9.99</p>
</label>
<label class="block space-y-2.5 px-4 rounded-2xl border border-gray-200 shadow-sm shadow-gray-950/5 py-3 hover:bg-gray-50 [--icon-display:none] has-checked:border-transparent has-checked:[--icon-display:block] has-checked:outline has-checked:outline-2 has-checked:outline-indigo-600 dark:hover:bg-gray-800/50 dark:border-gray-800" for="business2">
<input type="radio" id="business2" name="plan" hidden />
<div class="flex justify-between">
<p class="text-sm text-gray-950 font-medium dark:text-white">6-Core CPU</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5 text-primary-600 [display:var(--icon-display)]">
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
</svg>
</div>
<p class="mt-1 text-gray-500 text-sm">For startups and small businesses</p>
<p class="mt-4 text-gray-600 text-sm dark:text-gray-400">$39.99</p>
</label>
</form>
Subscribe form
Apply a focus effect on the container when the input is focused or focus-visible.
<div class="grid grid-cols-[1fr_auto] items-center rounded-full w-full max-w-sm pr-1 text-sm border border-gray-200 shadow-sm shadow-gray-950/5 has-[input:focus]:ring-2 has-[input:focus]:ring-primary-600 has-[input:focus]:border-transparent dark:bg-gray-800/25 dark:border-gray-800">
<input
type="email"
class="h-10 pl-4 text-sm bg-transparent outline-hidden peer"
placeholder="Enter your email"
required
/>
<button class="h-8 px-3.5 rounded-full flex items-center justify-center bg-indigo-600 text-white hover:brightness-90 transition-[filter] duration-300">Subscribe</button>
</div>
Each example demonstrates the has-{modifier}
variant’s utility for dynamic state styling, simplifying parent-child state interactions.