Filter chips where one choice is active at a time. The chosen chip tints with the accent and a checkmark slides in and draws itself, widening the pill. Built on label:has(:checked).
Also known as: filter chips css, pill radio buttons css, radio buttons as tags.
<div class="rad-radio-chips" role="radiogroup" aria-label="Category"> <label><input type="radio" name="rad-radio-chips" value="all" checked><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>All</label> <label><input type="radio" name="rad-radio-chips" value="design"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>Design</label> <label><input type="radio" name="rad-radio-chips" value="code"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>Code</label> <label><input type="radio" name="rad-radio-chips" value="writing"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>Writing</label> </div>
.rad-radio-chips {
--c: var(--rad-color, #3b82f6);
font-size: var(--rad-size, 18px);
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5em;
max-width: 22em;
font-weight: 500;
font-family: system-ui, sans-serif;
line-height: 1.2;
}
.rad-radio-chips label {
position: relative;
display: inline-flex;
align-items: center;
padding: 0.45em 0.95em;
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
border-radius: 99em;
font-size: 0.9em;
cursor: pointer;
transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease, padding 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.rad-radio-chips label:hover {
background: color-mix(in srgb, currentColor 6%, transparent);
}
.rad-radio-chips label:has(:checked) {
padding-left: 0.7em;
border-color: var(--c);
background: color-mix(in srgb, var(--c) 14%, transparent);
color: var(--c);
}
.rad-radio-chips label:has(:focus-visible) {
outline: 0.12em solid var(--c);
outline-offset: 0.15em;
}
.rad-radio-chips input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.rad-radio-chips svg {
width: 0;
height: 1em;
fill: none;
stroke: currentColor;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 20;
stroke-dashoffset: 20;
transition: width 0.3s cubic-bezier(0.65, 0, 0.35, 1), margin 0.3s cubic-bezier(0.65, 0, 0.35, 1), stroke-dashoffset 0.2s ease;
}
.rad-radio-chips input:checked + svg {
width: 1em;
margin-right: 0.35em;
stroke-dashoffset: 0;
transition: width 0.3s cubic-bezier(0.65, 0, 0.35, 1), margin 0.3s cubic-bezier(0.65, 0, 0.35, 1), stroke-dashoffset 0.3s ease 0.18s;
}
@media (prefers-reduced-motion: reduce) {
.rad-radio-chips, .rad-radio-chips *, .rad-radio-chips::before, .rad-radio-chips::after, .rad-radio-chips *::before, .rad-radio-chips *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}The same effect using Tailwind utility classes. No custom CSS or config needed.
<div role="radiogroup" aria-label="Category" class="flex flex-wrap justify-center gap-2">
<label class="relative inline-flex cursor-pointer items-center rounded-full border border-gray-300 px-4 py-1.5 text-sm transition-all duration-300 hover:bg-gray-100 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-500/15 has-[:checked]:pl-3 has-[:checked]:text-blue-600 has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-blue-500">
<input type="radio" name="category" value="all" checked class="peer absolute inset-0 cursor-pointer opacity-0">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-0 transition-all duration-300 peer-checked:mr-1.5 peer-checked:w-4" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
All
</label>
<label class="relative inline-flex cursor-pointer items-center rounded-full border border-gray-300 px-4 py-1.5 text-sm transition-all duration-300 hover:bg-gray-100 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-500/15 has-[:checked]:pl-3 has-[:checked]:text-blue-600 has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-blue-500">
<input type="radio" name="category" value="design" class="peer absolute inset-0 cursor-pointer opacity-0">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-0 transition-all duration-300 peer-checked:mr-1.5 peer-checked:w-4" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
Design
</label>
<label class="relative inline-flex cursor-pointer items-center rounded-full border border-gray-300 px-4 py-1.5 text-sm transition-all duration-300 hover:bg-gray-100 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-500/15 has-[:checked]:pl-3 has-[:checked]:text-blue-600 has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-blue-500">
<input type="radio" name="category" value="code" class="peer absolute inset-0 cursor-pointer opacity-0">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-0 transition-all duration-300 peer-checked:mr-1.5 peer-checked:w-4" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
Code
</label>
<label class="relative inline-flex cursor-pointer items-center rounded-full border border-gray-300 px-4 py-1.5 text-sm transition-all duration-300 hover:bg-gray-100 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-500/15 has-[:checked]:pl-3 has-[:checked]:text-blue-600 has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-blue-500">
<input type="radio" name="category" value="writing" class="peer absolute inset-0 cursor-pointer opacity-0">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-0 transition-all duration-300 peer-checked:mr-1.5 peer-checked:w-4" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
Writing
</label>
</div>--rad-color on the element or a parent.
Currently previewing #3b82f6.--rad-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 18px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.