AI CSS Animations

Radio Button Chips

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.

HTML

<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>

CSS

.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;
  }
}

Tailwind CSS version

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>

Customizing this snippet

  • Color: set --rad-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --rad-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 18px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar radio buttons