The style for the label element itself applies but for the styled radio button it doesn't. as far as I could troubleshoot it's because the peer class can't be directly applied to nested elements but I believe there must be some workaround.
Can't get this to work:
https://play.tailwindcss.com/q2CNd9zkjp
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<fieldset class="space-y-2">
<legend class="text-sm text-gray-700 mb-1">Select Priority</legend>
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="peer hidden" checked />
<div class="flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400 peer-checked:border-blue-500 peer-checked:bg-blue-900 transition-all">
<!-- Outer Radio -->
<div class="w-5 h-5 flex items-center justify-center rounded-full border-2
border-gray-400 peer-checked:border-blue-500 transition-colors">
<!-- Inner Dot -->
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 scale-0 peer-checked:scale-100 transition-transform"></div>
</div>
<span class="text-sm">Low</span>
</div>
</label>
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Medium" class="peer hidden" />
<div class="flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400 peer-checked:border-blue-500 peer-checked:bg-blue-900 transition-all">
<div class="w-5 h-5 flex items-center justify-center rounded-full border-2
border-gray-400 peer-checked:border-blue-500 transition-colors">
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 scale-0 peer-checked:scale-100 transition-transform"></div>
</div>
<span class="text-sm">Medium</span>
</div>
</label>
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="High" class="peer hidden" />
<div class="flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400 peer-checked:border-blue-500 peer-checked:bg-blue-900 transition-all">
<div class="w-5 h-5 flex items-center justify-center rounded-full border-2
border-gray-400 peer-checked:border-blue-500 transition-colors">
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 scale-0 peer-checked:scale-100 transition-transform"></div>
</div>
<span class="text-sm">High</span>
</div>
</label>
</fieldset>
TLDR: It's not possible to target nested elements using peer
; only with group
. You can create a custom variant for group
that checks for the presence of input:checked
. See custom group-checked
variant in the last code snippet for reference.
The peer
class only affects direct sibling elements, not their child elements.
If you absolutely want to solve this using only peer
without any group
, you can still apply pseudo-elements like before
and after
on the sibling element. I won't elaborate on this example here - I just wanted to leave the idea itself. I believe my other examples using group are simpler and more reusable.
group-has-*
However, you can add a group
class to the <label>
. With group, you can leverage the CSS :has
selector using Tailwind's group-has-*
variant, which allows you to target the single input field that has the checked state: group-has-[input:checked]
<script src="https://cdn.tailwindcss.com"></script>
<fieldset class="space-y-2">
<!-- Example -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="peer hidden" checked />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
peer-checked:border-blue-500 peer-checked:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-has-[input:checked]:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-has-[input:checked]:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-has-[input:checked]:text-blue-200">Low</span>
</div>
</label>
<!-- Copy for Medium -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="peer hidden" />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
peer-checked:border-blue-500 peer-checked:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-has-[input:checked]:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-has-[input:checked]:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-has-[input:checked]:text-blue-200">Low</span>
</div>
</label>
</fieldset>
In this case, I would omit the peer
entirely.
<script src="https://cdn.tailwindcss.com"></script>
<fieldset class="space-y-2">
<!-- Example -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="hidden" checked />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
group-has-[input:checked]:border-blue-500 group-has-[input:checked]:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-has-[input:checked]:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-has-[input:checked]:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-has-[input:checked]:text-blue-200">Low</span>
</div>
</label>
<!-- Copy for Medium -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="hidden" />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
group-has-[input:checked]:border-blue-500 group-has-[input:checked]:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-has-[input:checked]:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-has-[input:checked]:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-has-[input:checked]:text-blue-200">Medium</span>
</div>
</label>
</fieldset>
group-checked
variant(alternative for group-has-[input:checked]
)
And you could even create a custom variant if needed.
@custom-variant
directive - TailwindCSS v4 Docs@custom-variant group-checked {
&:is(:where(.group):has(input:checked) *) {
@slot;
}
}
Now can refer to group-has-[input:checked]
as group-checked
, see:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@custom-variant group-checked {
&:is(:where(.group):has(input:checked) *) {
@slot;
}
}
</style>
<fieldset class="space-y-2">
<!-- Example -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="hidden" checked />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
group-checked:border-blue-500 group-checked:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-checked:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-checked:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-checked:text-blue-200">Low</span>
</div>
</label>
<!-- Copy for Medium -->
<label class="group block cursor-pointer">
<input type="radio" name="priority" value="Low" class="hidden" />
<div class="
flex items-center gap-3 p-3 rounded border border-gray-500 bg-transparent
group-hover:border-blue-400
group-checked:border-blue-500 group-checked:bg-blue-900
transition-all
">
<!-- Outer Radio -->
<div class="
w-5 h-5 flex items-center justify-center rounded-full border-2 border-gray-400
group-checked:border-blue-500 transition-colors
">
<!-- Inner Dot -->
<div class="
w-2.5 h-2.5 rounded-full bg-blue-500 scale-0
group-checked:scale-100 transition-transform
"></div>
</div>
<span class="text-sm group-checked:text-blue-200">Medium</span>
</div>
</label>
</fieldset>