10 / 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 Ink Splash Radio Animation
A Material-style ink ripple reimagined for radio rows: checking an option splashes ink from the indicator until it floods the row, while the dot stamps in like a wet press. The splash is the row's before: circle, scaled 0 → 34 by has-checked: with an asymmetric transition, so ink soaks in slowly and dries fast.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="trb-10 w-full min-h-svh grid place-items-center bg-trb10-bg px-6 py-14 font-trb10-sans text-trb10-body" aria-label="Ink splash radio buttons demo">
<fieldset class="w-full max-w-[26rem]">
<legend class="font-trb10-serif text-[30px] leading-tight font-medium tracking-[-0.01em]">Paper stock</legend>
<p class="mt-1 text-sm text-trb10-mut">Printed on FSC-certified stock.</p>
<div class="mt-5 grid gap-2">
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="matte" class="sr-only" checked>
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Matte</span><span class="text-[13px] text-trb10-mut">Soft, no glare</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">120 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="satin" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Satin</span><span class="text-[13px] text-trb10-mut">Gentle sheen</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">150 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="gloss" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Gloss</span><span class="text-[13px] text-trb10-mut">Vivid, reflective</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">170 gsm</span>
</label>
</div>
</fieldset>
</section><section class="trb-10 w-full min-h-svh grid place-items-center bg-trb10-bg px-6 py-14 font-trb10-sans text-trb10-body" aria-label="Ink splash radio buttons demo">
<fieldset class="w-full max-w-[26rem]">
<legend class="font-trb10-serif text-[30px] leading-tight font-medium tracking-[-0.01em]">Paper stock</legend>
<p class="mt-1 text-sm text-trb10-mut">Printed on FSC-certified stock.</p>
<div class="mt-5 grid gap-2">
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="matte" class="sr-only" checked>
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Matte</span><span class="text-[13px] text-trb10-mut">Soft, no glare</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">120 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="satin" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Satin</span><span class="text-[13px] text-trb10-mut">Gentle sheen</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">150 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="gloss" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Gloss</span><span class="text-[13px] text-trb10-mut">Vivid, reflective</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">170 gsm</span>
</label>
</div>
</fieldset>
</section>@import "tailwindcss";
@theme {
--color-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
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-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
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=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&family=Geist:wght@400..700&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=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&family=Geist:wght@400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The splash moved from a
.row::beforerule tobefore:utilities on the row; the two-speed transition is two arbitrary[transition:…]values, one per state. isolate+before:-z-10replaced the original's per-childz-index:1rules — the ink sits under all content in one stroke.- UI upgrade from the CSS original: a trailing paper weight (120 / 150 / 170 gsm) gives the choice a measurable difference, and titles moved to Newsreader for a print-shop voice.
- UI upgrade: sublines rose from
#8d8471(3.5:1) to#6a6150(5.8:1 on the ink wash), and the ring from 2.2:1 to 3.1:1.
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 Ink Splash Radio Animation
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-ink-splash-radio-animation/
A Material-style ink ripple reimagined for radio rows: checking an option splashes ink from the indicator until it floods the row, while the dot stamps in like a wet press. The splash is the row's before: circle, scaled 0 → 34 by has-checked: with an asymmetric transition, so ink soaks in slowly and dries fast.
## HTML
```html
<section class="trb-10 w-full min-h-svh grid place-items-center bg-trb10-bg px-6 py-14 font-trb10-sans text-trb10-body" aria-label="Ink splash radio buttons demo">
<fieldset class="w-full max-w-[26rem]">
<legend class="font-trb10-serif text-[30px] leading-tight font-medium tracking-[-0.01em]">Paper stock</legend>
<p class="mt-1 text-sm text-trb10-mut">Printed on FSC-certified stock.</p>
<div class="mt-5 grid gap-2">
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="matte" class="sr-only" checked>
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Matte</span><span class="text-[13px] text-trb10-mut">Soft, no glare</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">120 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="satin" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Satin</span><span class="text-[13px] text-trb10-mut">Gentle sheen</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">150 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="gloss" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Gloss</span><span class="text-[13px] text-trb10-mut">Vivid, reflective</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">170 gsm</span>
</label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
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 Ink Splash Radio Animation
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-ink-splash-radio-animation/
A Material-style ink ripple reimagined for radio rows: checking an option splashes ink from the indicator until it floods the row, while the dot stamps in like a wet press. The splash is the row's before: circle, scaled 0 → 34 by has-checked: with an asymmetric transition, so ink soaks in slowly and dries fast.
## HTML
```html
<section class="trb-10 w-full min-h-svh grid place-items-center bg-trb10-bg px-6 py-14 font-trb10-sans text-trb10-body" aria-label="Ink splash radio buttons demo">
<fieldset class="w-full max-w-[26rem]">
<legend class="font-trb10-serif text-[30px] leading-tight font-medium tracking-[-0.01em]">Paper stock</legend>
<p class="mt-1 text-sm text-trb10-mut">Printed on FSC-certified stock.</p>
<div class="mt-5 grid gap-2">
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="matte" class="sr-only" checked>
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Matte</span><span class="text-[13px] text-trb10-mut">Soft, no glare</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">120 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="satin" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Satin</span><span class="text-[13px] text-trb10-mut">Gentle sheen</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">150 gsm</span>
</label>
<label class="group relative isolate flex min-h-16 cursor-pointer items-center gap-3.5 overflow-hidden rounded-2xl border-[1.5px] border-trb10-line bg-trb10-card px-[18px] transition-colors duration-300 before:pointer-events-none before:absolute before:top-1/2 before:left-7 before:-z-10 before:-m-[15px] before:size-[30px] before:scale-0 before:rounded-full before:bg-[radial-gradient(circle,oklch(0.45_0.16_265/0.12),oklch(0.45_0.16_265/0.09)_55%,oklch(0.45_0.16_265/0.07)_80%,transparent_97%)] before:opacity-0 before:[transition:scale_.2s,opacity_.3s] before:content-[''] hover:border-trb10-ink/35 has-checked:border-trb10-ink/55 has-checked:before:scale-[34] has-checked:before:opacity-100 has-checked:before:[transition:scale_.8s_cubic-bezier(.22,1,.36,1),opacity_.2s] has-focus-visible:outline-3 has-focus-visible:outline-offset-2 has-focus-visible:outline-trb10-ink motion-reduce:has-checked:before:[transition:opacity_.2s]">
<input type="radio" name="trb-10" value="gloss" class="sr-only">
<span aria-hidden="true" class="grid size-[22px] flex-none place-items-center rounded-full border-2 border-trb10-ring transition-colors duration-300 after:size-[11px] after:scale-0 after:rounded-full after:bg-trb10-ink after:transition-transform after:duration-350 after:ease-trb10-stamp after:content-[''] group-has-checked:border-trb10-ink group-has-checked:after:scale-100 motion-reduce:after:transition-none"></span>
<span class="grid flex-1"><span class="font-trb10-serif text-lg font-semibold">Gloss</span><span class="text-[13px] text-trb10-mut">Vivid, reflective</span></span>
<span class="text-xs font-semibold tracking-[0.06em] text-trb10-mut uppercase">170 gsm</span>
</label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb10-bg: #f6f1e7;
--color-trb10-bg: oklch(0.958 0.016 85);
--color-trb10-card: #fffdf8;
--color-trb10-card: oklch(0.995 0.006 85);
--color-trb10-body: #2a2620;
--color-trb10-body: oklch(0.27 0.012 75);
--color-trb10-mut: #6a6150;
--color-trb10-mut: oklch(0.5 0.025 80);
--color-trb10-line: #e7dfd0;
--color-trb10-line: oklch(0.91 0.02 85);
--color-trb10-ring: #a99d84;
--color-trb10-ring: oklch(0.7 0.03 85);
--color-trb10-ink: #3a47a8;
--color-trb10-ink: oklch(0.45 0.16 265);
--ease-trb10-stamp: cubic-bezier(0.34, 1.7, 0.5, 1);
--font-trb10-serif: 'Newsreader', Georgia, serif;
--font-trb10-sans: 'Geist', system-ui, sans-serif;
}
/* 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-10 {
width: 100%;
min-height: 100svh;
}
```How this works
Asymmetric timing is the effect. Tailwind's duration-* sets one duration for every property. The soak needs a slow scale and a fast opacity, so both states carry an arbitrary [transition:…] — the checked state's value applies while entering, the base value while leaving.
Sized to clear the far corner. A 30px circle at scale-[34] is 1020px across — enough to reach the far edge of a 26rem row from the indicator. Undershoot and the wash dies mid-row as a visible band.
Make it yours
- Ink colour:
--color-trb10-ink; the wash derives from it at 7–12% alpha. - Faster soak: shorten
.8sin the checked transition. - Per-row inks: override
--color-trb10-inkon a label with an arbitrary property.
Gotchas — read before shipping
- The row needs
overflow-hiddenor the ink shows square outside the rounded corners. - v4's
scale-*writes thescaleproperty — transitionscale, nottransform. - Keep the wash under ~12% so text contrast survives on top of it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Without :has() the rows select instantly, with no splash.