07 / 24
All 24 in 24 Tailwind CSS Radio Buttons
- 01Tailwind Radio Button Cards (Selectable Cards)
- 02Tailwind Pill Radio Toggle (Segmented Control)
- 03Tailwind Radio Button with Icon and Description
- 04Tailwind Dark Mode Glowing Radio Button
- 05Accessible Tailwind Radio Button (appearance-none)
- 06Tailwind Radio Group with Badges
- 07Tailwind Animated Radio Button (Concentric Ripple)
- 08Tailwind Sliding Pill Radio Switch
- 09Tailwind 3D Sphere Radio Buttons
- 10Tailwind Ink Splash Radio Animation
- 11Tailwind Star Rating Radio Buttons
- 12Tailwind 3D Tilt Card Radio Selector
- 13Minimal Checkmark Radio Button in Tailwind
- 14Tailwind Color Swatch Radio Picker
- 15Tailwind Segmented Control Bar (View Switcher)
- 16Tailwind Neon Cyberpunk Radio Buttons
- 17Tailwind Pricing Plan Radio Cards
- 18Tailwind Liquid Wave Fill Radio Button
- 19Tailwind Image Radio Button Selector (Arch Mask)
- 20Tailwind Animated Gradient Border Radio Cards
- 21Tailwind Avatar Radio Picker (Profile Selector)
- 22Tailwind Stacked Radio List (Payment Method)
- 23Tailwind 3D Push Button Radio (Tactile Keys)
- 24Tailwind Pulsing Glow Radio Button (Status Picker)
Tailwind Animated Radio Button (Concentric Ripple)
A radio with physicality: on check two concentric rings burst outward from the dot like a droplet hitting water — the input's own after: ring, chased 120ms later by a sibling ring driven with peer-checked:. Both are one-shot animate-* tokens, so re-checking replays the splash for free.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="trb-07 w-full min-h-svh grid place-items-center bg-trb07-bg px-6 py-14 font-trb07-sans text-trb07-ink" aria-label="Concentric ripple radio buttons demo">
<fieldset class="w-full max-w-[22rem] rounded-2xl bg-white/80 p-5 shadow-trb07-card ring-1 ring-trb07-line">
<legend class="sr-only">Meeting length</legend>
<p aria-hidden="true" class="px-2 text-xl font-bold tracking-[-0.02em]">Meeting length</p>
<div class="mt-2 grid">
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="15" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">15 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Quick sync</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="30" checked class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">30 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Most booked</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="60" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">1 hour</span><span class="ml-auto text-[13px] text-trb07-mut">Deep dive</span></label>
</div>
</fieldset>
</section><section class="trb-07 w-full min-h-svh grid place-items-center bg-trb07-bg px-6 py-14 font-trb07-sans text-trb07-ink" aria-label="Concentric ripple radio buttons demo">
<fieldset class="w-full max-w-[22rem] rounded-2xl bg-white/80 p-5 shadow-trb07-card ring-1 ring-trb07-line">
<legend class="sr-only">Meeting length</legend>
<p aria-hidden="true" class="px-2 text-xl font-bold tracking-[-0.02em]">Meeting length</p>
<div class="mt-2 grid">
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="15" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">15 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Quick sync</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="30" checked class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">30 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Most booked</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="60" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">1 hour</span><span class="ml-auto text-[13px] text-trb07-mut">Deep dive</span></label>
</div>
</fieldset>
</section>@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..800&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.input:checked + .ringbecamepeer-checked:on the ring; the 120ms delay moved into the--animate-trb07-burst-latetoken instead of a second declaration.- The reduced-motion block became two
motion-reduce:…:animate-noneutilities at the exact elements that animate. - UI upgrade from the CSS original: the list sits on a card with trailing context per option ("Most booked"), the checked row gets a faint wash and semibold label, and
forced-colors:appearance-autokeeps it usable in High Contrast. - UI upgrade: the unchecked ring darkened from
#a9b6c4(2.1:1) to 3.1:1 against white, meeting WCAG 1.4.11 for UI components.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Animated Radio Button (Concentric Ripple)
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-animated-radio-button-concentric-ripple/
A radio with physicality: on check two concentric rings burst outward from the dot like a droplet hitting water — the input's own after: ring, chased 120ms later by a sibling ring driven with peer-checked:. Both are one-shot animate-* tokens, so re-checking replays the splash for free.
## HTML
```html
<section class="trb-07 w-full min-h-svh grid place-items-center bg-trb07-bg px-6 py-14 font-trb07-sans text-trb07-ink" aria-label="Concentric ripple radio buttons demo">
<fieldset class="w-full max-w-[22rem] rounded-2xl bg-white/80 p-5 shadow-trb07-card ring-1 ring-trb07-line">
<legend class="sr-only">Meeting length</legend>
<p aria-hidden="true" class="px-2 text-xl font-bold tracking-[-0.02em]">Meeting length</p>
<div class="mt-2 grid">
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="15" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">15 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Quick sync</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="30" checked class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">30 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Most booked</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="60" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">1 hour</span><span class="ml-auto text-[13px] text-trb07-mut">Deep dive</span></label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Animated Radio Button (Concentric Ripple)
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-animated-radio-button-concentric-ripple/
A radio with physicality: on check two concentric rings burst outward from the dot like a droplet hitting water — the input's own after: ring, chased 120ms later by a sibling ring driven with peer-checked:. Both are one-shot animate-* tokens, so re-checking replays the splash for free.
## HTML
```html
<section class="trb-07 w-full min-h-svh grid place-items-center bg-trb07-bg px-6 py-14 font-trb07-sans text-trb07-ink" aria-label="Concentric ripple radio buttons demo">
<fieldset class="w-full max-w-[22rem] rounded-2xl bg-white/80 p-5 shadow-trb07-card ring-1 ring-trb07-line">
<legend class="sr-only">Meeting length</legend>
<p aria-hidden="true" class="px-2 text-xl font-bold tracking-[-0.02em]">Meeting length</p>
<div class="mt-2 grid">
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="15" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">15 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Quick sync</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="30" checked class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">30 minutes</span><span class="ml-auto text-[13px] text-trb07-mut">Most booked</span></label>
<label class="group flex min-h-[3.25rem] cursor-pointer items-center gap-3.5 rounded-xl px-2 transition-colors hover:bg-trb07-bg has-checked:bg-trb07-acc/6"><span class="relative size-6 flex-none"><input type="radio" name="trb-07" value="60" class="peer relative z-2 grid size-6 cursor-pointer appearance-none place-items-center rounded-full border-2 border-trb07-ring bg-white transition-colors before:size-3 before:scale-0 before:rounded-full before:bg-trb07-acc before:transition-transform before:duration-300 before:ease-trb07-spring before:content-[''] after:pointer-events-none after:absolute after:-inset-0.5 after:rounded-full after:border-2 after:border-trb07-acc after:opacity-0 after:content-[''] checked:border-trb07-acc checked:before:scale-100 checked:after:animate-trb07-burst focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-trb07-acc motion-reduce:checked:after:animate-none forced-colors:appearance-auto"><span aria-hidden="true" class="pointer-events-none absolute inset-0 z-1 rounded-full border-2 border-trb07-acc/70 opacity-0 peer-checked:animate-trb07-burst-late motion-reduce:peer-checked:animate-none"></span></span><span class="text-[15.5px] group-has-checked:font-semibold">1 hour</span><span class="ml-auto text-[13px] text-trb07-mut">Deep dive</span></label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb07-bg: #eef5f9;
--color-trb07-bg: oklch(0.965 0.01 225);
--color-trb07-ink: #1d2530;
--color-trb07-ink: oklch(0.25 0.02 250);
--color-trb07-mut: #5b6776;
--color-trb07-mut: oklch(0.51 0.025 250);
--color-trb07-line: #dde7ee;
--color-trb07-line: oklch(0.925 0.012 230);
--color-trb07-ring: #98a7b7;
--color-trb07-ring: oklch(0.72 0.03 245);
--color-trb07-acc: #1690c4;
--color-trb07-acc: oklch(0.6 0.14 230);
--shadow-trb07-card: 0 14px 40px -20px oklch(0.3 0.05 240 / 0.3);
--ease-trb07-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--font-trb07-sans: 'Geist', system-ui, sans-serif;
--animate-trb07-burst: trb07-burst 0.7s cubic-bezier(0.22, 1, 0.36, 1);
--animate-trb07-burst-late: trb07-burst 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.12s;
@keyframes trb07-burst {
0% {
transform: scale(0.4);
opacity: 0.9;
}
100% {
transform: scale(2.6);
opacity: 0;
}
}
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.trb-07 {
width: 100%;
min-height: 100svh;
}
```How this works
Two rings, two variant families. The first ring is the input's after: pseudo-element, animated with checked:after:. The second is a real <span> after the input; marking the input peer lets the span read its state with peer-checked:.
--animate-trb07-burst: trb07-burst .7s cubic-bezier(.22,1,.36,1);
--animate-trb07-burst-late: trb07-burst .8s cubic-bezier(.22,1,.36,1) .12s;Why one-shot. An animation applied by a state variant runs each time the state begins, so switching away and back replays the burst — no class toggling.
Make it yours
- Bigger splash: raise the keyframe's end scale from 2.6 to 3.2.
- Third ring: add another sibling span with a third token delayed .24s.
- Accent:
--color-trb07-acc.
Gotchas — read before shipping
- Rings must be
pointer-events-noneso a mid-burst click lands on the input. peeronly reaches later siblings — the ring span must follow the input in the DOM.- Give the input
relative z-2so the rings pass behind the dot.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
No :has() at all — appearance-none, pseudo-elements and keyframes only.