20 / 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 Gradient Border Radio Cards
Dark glass cards wrapped in a living gradient border: at rest a faint rim; on selection a vivid violet-to-cyan gradient circulates around the perimeter. The port uses the two-background trick — [background:linear-gradient(card,card)_padding-box,conic-gradient(from_var(--trb20-angle),…)_border-box] on a transparent border — so there's no wrapper element, and @property animates the angle.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="trb-20 w-full min-h-svh grid place-items-center bg-trb20-bg px-6 py-14 font-trb20-sans text-trb20-ink" aria-label="Gradient border card radio demo">
<fieldset>
<legend class="text-center text-[26px] font-bold tracking-[-0.02em]">Choose your model</legend>
<div class="mt-8 flex flex-wrap justify-center gap-5">
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="lite" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Lite</span>
<span class="text-[13px] text-trb20-mut">Fast drafts & chat</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">128k context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="pro" class="sr-only" checked>
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Pro</span>
<span class="text-[13px] text-trb20-mut">Deep reasoning</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">1M context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="vision" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Vision</span>
<span class="text-[13px] text-trb20-mut">Image + video in</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">Multimodal</span>
</span>
</label>
</div>
</fieldset>
</section><section class="trb-20 w-full min-h-svh grid place-items-center bg-trb20-bg px-6 py-14 font-trb20-sans text-trb20-ink" aria-label="Gradient border card radio demo">
<fieldset>
<legend class="text-center text-[26px] font-bold tracking-[-0.02em]">Choose your model</legend>
<div class="mt-8 flex flex-wrap justify-center gap-5">
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="lite" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Lite</span>
<span class="text-[13px] text-trb20-mut">Fast drafts & chat</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">128k context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="pro" class="sr-only" checked>
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Pro</span>
<span class="text-[13px] text-trb20-mut">Deep reasoning</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">1M context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="vision" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Vision</span>
<span class="text-[13px] text-trb20-mut">Image + video in</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">Multimodal</span>
</span>
</label>
</div>
</fieldset>
</section>@import "tailwindcss";
@theme {
--color-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
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-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
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&family=Geist+Mono:wght@400;500&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&family=Geist+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The rim wrapper + inner panel pair collapsed into one element using padding-box / border-box backgrounds, written as one arbitrary
[background:…]property per state. @propertyis the only rule outside@theme; the keyframe that drives it lives in the token block.- UI upgrade from the CSS original: a status dot lights cyan on the chosen model — a second, shape-based signal beside the rim — and stats moved to Geist Mono.
- UI upgrade: the stat line rose from
#6a7194(3.6:1) to 7:1 and meta copy to 7:1; the hover lift respects reduced motion.
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 Gradient Border Radio Cards
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-animated-gradient-border-radio-cards/
Dark glass cards wrapped in a living gradient border: at rest a faint rim; on selection a vivid violet-to-cyan gradient circulates around the perimeter. The port uses the two-background trick — [background:linear-gradient(card,card)_padding-box,conic-gradient(from_var(--trb20-angle),…)_border-box] on a transparent border — so there's no wrapper element, and @property animates the angle.
## HTML
```html
<section class="trb-20 w-full min-h-svh grid place-items-center bg-trb20-bg px-6 py-14 font-trb20-sans text-trb20-ink" aria-label="Gradient border card radio demo">
<fieldset>
<legend class="text-center text-[26px] font-bold tracking-[-0.02em]">Choose your model</legend>
<div class="mt-8 flex flex-wrap justify-center gap-5">
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="lite" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Lite</span>
<span class="text-[13px] text-trb20-mut">Fast drafts & chat</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">128k context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="pro" class="sr-only" checked>
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Pro</span>
<span class="text-[13px] text-trb20-mut">Deep reasoning</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">1M context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="vision" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Vision</span>
<span class="text-[13px] text-trb20-mut">Image + video in</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">Multimodal</span>
</span>
</label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
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 Gradient Border Radio Cards
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-animated-gradient-border-radio-cards/
Dark glass cards wrapped in a living gradient border: at rest a faint rim; on selection a vivid violet-to-cyan gradient circulates around the perimeter. The port uses the two-background trick — [background:linear-gradient(card,card)_padding-box,conic-gradient(from_var(--trb20-angle),…)_border-box] on a transparent border — so there's no wrapper element, and @property animates the angle.
## HTML
```html
<section class="trb-20 w-full min-h-svh grid place-items-center bg-trb20-bg px-6 py-14 font-trb20-sans text-trb20-ink" aria-label="Gradient border card radio demo">
<fieldset>
<legend class="text-center text-[26px] font-bold tracking-[-0.02em]">Choose your model</legend>
<div class="mt-8 flex flex-wrap justify-center gap-5">
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="lite" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Lite</span>
<span class="text-[13px] text-trb20-mut">Fast drafts & chat</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">128k context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="pro" class="sr-only" checked>
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Pro</span>
<span class="text-[13px] text-trb20-mut">Deep reasoning</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">1M context</span>
</span>
</label>
<label class="group relative cursor-pointer">
<input type="radio" name="trb-20" value="vision" class="sr-only">
<span class="relative grid w-[12.5rem] gap-1 rounded-[18px] border-[1.5px] border-transparent [background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),rgb(255_255_255/0.14),rgb(255_255_255/0.05),rgb(255_255_255/0.14))_border-box] px-5 py-6 transition-[translate] duration-300 group-hover:-translate-y-0.5 group-has-checked:animate-trb20-rotate group-has-checked:[background:linear-gradient(170deg,#171927,#101119)_padding-box,conic-gradient(from_var(--trb20-angle),var(--color-trb20-g1),var(--color-trb20-g2),var(--color-trb20-g1))_border-box] group-has-focus-visible:outline-3 group-has-focus-visible:outline-offset-3 group-has-focus-visible:outline-white motion-reduce:group-has-checked:animate-none motion-reduce:group-hover:translate-y-0">
<span aria-hidden="true" class="absolute top-5 right-5 size-2 rounded-full bg-white/15 group-has-checked:bg-trb20-g2 group-has-checked:shadow-trb20-dot"></span>
<span class="text-lg font-bold tracking-[-0.01em] text-white">Nova Vision</span>
<span class="text-[13px] text-trb20-mut">Image + video in</span>
<span class="mt-3 font-trb20-mono text-xs font-medium tracking-[0.06em] text-trb20-mut uppercase group-has-checked:text-trb20-g2">Multimodal</span>
</span>
</label>
</div>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb20-bg: #0b0c12;
--color-trb20-bg: oklch(0.155 0.01 275);
--color-trb20-ink: #edeffa;
--color-trb20-ink: oklch(0.95 0.012 275);
--color-trb20-mut: #979db8;
--color-trb20-mut: oklch(0.7 0.03 275);
--color-trb20-g1: #9b3ff0;
--color-trb20-g1: oklch(0.6 0.24 300);
--color-trb20-g2: #35c6d8;
--color-trb20-g2: oklch(0.75 0.12 205);
--shadow-trb20-dot: 0 0 10px oklch(0.75 0.12 205 / 0.8);
--font-trb20-sans: 'Geist', system-ui, sans-serif;
--font-trb20-mono: 'Geist Mono', ui-monospace, monospace;
--animate-trb20-rotate: trb20-rotate 5s linear infinite;
@keyframes trb20-rotate {
to {
--trb20-angle: 360deg;
}
}
}
/* Registered so the conic angle interpolates. */
@property --trb20-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 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-20 {
width: 100%;
min-height: 100svh;
}
```How this works
Two backgrounds, one border. The transparent 1.5px border exposes the border-box layer (the conic gradient); the padding-box layer paints the card over everything inside. The original needed an outer rim element and an inner panel with a hand-matched radius.
Registered angle. @property makes --trb20-angle an interpolable <angle>; the keyframe rotates it. Unchecked cards keep a static rim and never animate.
Make it yours
- Gradient stops:
--color-trb20-g1/--color-trb20-g2. - Rim thickness:
border-[1.5px]. - Rotation speed:
5sin--animate-trb20-rotate.
Gotchas — read before shipping
- Keep the gradient on the same element that runs the animation — the animated
--trb20-angleis set there and doesn't inherit (inherits: false). - Without
@propertythe checked rim is static but present — an intentional fallback. - Gate the animation to the checked card only; rotating every card multiplies repaints.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
@property floors match Tailwind v4's own; below them the rim is static.