11 / 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 Star Rating Radio Buttons
A five-star rating built the right way: five radios in logical 1→5 order, cumulative fill, a hover preview and a staggered pop when the rating lands — plus a live caption ("Great") for the score. The cumulative logic is one arbitrary variant, group-has-[~label_input:checked]/star:: every star before the checked one lights up.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="trb-11 w-full min-h-svh grid place-items-center bg-trb11-bg px-6 py-14 font-trb11-sans text-trb11-ink" aria-label="Star rating radio group demo">
<fieldset class="group/rate grid justify-items-center gap-4 rounded-3xl bg-white px-8 py-7 shadow-trb11-card ring-1 ring-trb11-line">
<legend class="sr-only">Rate your experience</legend>
<p aria-hidden="true" class="text-center text-[22px] font-extrabold tracking-[-0.02em]">Rate your experience</p>
<div class="group/stars flex gap-1">
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="1" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:0ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">1 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="2" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:60ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">2 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="3" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:120ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">3 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="4" class="sr-only" checked><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:180ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">4 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="5" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:240ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">5 of 5 stars</span></label>
</div>
<p aria-hidden="true" class="min-h-7 text-center text-lg font-bold text-trb11-ink">
<span class="hidden group-has-[[value='1']:checked]/rate:inline">Poor</span><span class="hidden group-has-[[value='2']:checked]/rate:inline">Fair</span><span class="hidden group-has-[[value='3']:checked]/rate:inline">Good</span><span class="hidden group-has-[[value='4']:checked]/rate:inline">Great</span><span class="hidden group-has-[[value='5']:checked]/rate:inline">Loved it</span>
</p>
<p class="text-[13px] text-trb11-mut">Hover to preview · click or use arrow keys to rate</p>
</fieldset>
</section><section class="trb-11 w-full min-h-svh grid place-items-center bg-trb11-bg px-6 py-14 font-trb11-sans text-trb11-ink" aria-label="Star rating radio group demo">
<fieldset class="group/rate grid justify-items-center gap-4 rounded-3xl bg-white px-8 py-7 shadow-trb11-card ring-1 ring-trb11-line">
<legend class="sr-only">Rate your experience</legend>
<p aria-hidden="true" class="text-center text-[22px] font-extrabold tracking-[-0.02em]">Rate your experience</p>
<div class="group/stars flex gap-1">
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="1" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:0ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">1 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="2" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:60ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">2 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="3" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:120ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">3 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="4" class="sr-only" checked><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:180ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">4 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="5" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:240ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">5 of 5 stars</span></label>
</div>
<p aria-hidden="true" class="min-h-7 text-center text-lg font-bold text-trb11-ink">
<span class="hidden group-has-[[value='1']:checked]/rate:inline">Poor</span><span class="hidden group-has-[[value='2']:checked]/rate:inline">Fair</span><span class="hidden group-has-[[value='3']:checked]/rate:inline">Good</span><span class="hidden group-has-[[value='4']:checked]/rate:inline">Great</span><span class="hidden group-has-[[value='5']:checked]/rate:inline">Loved it</span>
</p>
<p class="text-[13px] text-trb11-mut">Hover to preview · click or use arrow keys to rate</p>
</fieldset>
</section>@import "tailwindcss";
@theme {
--color-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
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-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
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=Plus+Jakarta+Sans: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=Plus+Jakarta+Sans:wght@400..800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Five nth-child animation-delay rules became one
@theme inlinetoken readingvar(--d)and a[--d:…]class per star. - The original's hover reset needed
!importantin CSS too; in Tailwind the three tiers are explicit — normal,group-hover/stars:…!, and a more specific arbitrary[label:hover_&]:…!. - UI upgrade from the CSS original: a verbal caption (Poor → Loved it) now names the score, switched by
group-has-[[value='N']:checked]/rate:, and the control sits on a card. - UI upgrade: empty-star strokes rose from
#d3c9ae(1.6:1) to 3:1, and the hint from#a49879(2.8:1) to 5.4: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 Star Rating Radio Buttons
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-star-rating-radio-buttons/
A five-star rating built the right way: five radios in logical 1→5 order, cumulative fill, a hover preview and a staggered pop when the rating lands — plus a live caption ("Great") for the score. The cumulative logic is one arbitrary variant, group-has-[~label_input:checked]/star:: every star before the checked one lights up.
## HTML
```html
<section class="trb-11 w-full min-h-svh grid place-items-center bg-trb11-bg px-6 py-14 font-trb11-sans text-trb11-ink" aria-label="Star rating radio group demo">
<fieldset class="group/rate grid justify-items-center gap-4 rounded-3xl bg-white px-8 py-7 shadow-trb11-card ring-1 ring-trb11-line">
<legend class="sr-only">Rate your experience</legend>
<p aria-hidden="true" class="text-center text-[22px] font-extrabold tracking-[-0.02em]">Rate your experience</p>
<div class="group/stars flex gap-1">
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="1" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:0ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">1 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="2" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:60ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">2 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="3" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:120ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">3 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="4" class="sr-only" checked><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:180ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">4 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="5" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:240ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">5 of 5 stars</span></label>
</div>
<p aria-hidden="true" class="min-h-7 text-center text-lg font-bold text-trb11-ink">
<span class="hidden group-has-[[value='1']:checked]/rate:inline">Poor</span><span class="hidden group-has-[[value='2']:checked]/rate:inline">Fair</span><span class="hidden group-has-[[value='3']:checked]/rate:inline">Good</span><span class="hidden group-has-[[value='4']:checked]/rate:inline">Great</span><span class="hidden group-has-[[value='5']:checked]/rate:inline">Loved it</span>
</p>
<p class="text-[13px] text-trb11-mut">Hover to preview · click or use arrow keys to rate</p>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
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 Star Rating Radio Buttons
Source: https://codefronts.com/components/tailwind-css-radio-buttons/tailwind-star-rating-radio-buttons/
A five-star rating built the right way: five radios in logical 1→5 order, cumulative fill, a hover preview and a staggered pop when the rating lands — plus a live caption ("Great") for the score. The cumulative logic is one arbitrary variant, group-has-[~label_input:checked]/star:: every star before the checked one lights up.
## HTML
```html
<section class="trb-11 w-full min-h-svh grid place-items-center bg-trb11-bg px-6 py-14 font-trb11-sans text-trb11-ink" aria-label="Star rating radio group demo">
<fieldset class="group/rate grid justify-items-center gap-4 rounded-3xl bg-white px-8 py-7 shadow-trb11-card ring-1 ring-trb11-line">
<legend class="sr-only">Rate your experience</legend>
<p aria-hidden="true" class="text-center text-[22px] font-extrabold tracking-[-0.02em]">Rate your experience</p>
<div class="group/stars flex gap-1">
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="1" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:0ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">1 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="2" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:60ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">2 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="3" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:120ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">3 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="4" class="sr-only" checked><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:180ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">4 of 5 stars</span></label>
<label class="group/star relative grid size-[52px] cursor-pointer place-items-center rounded-xl has-focus-visible:outline-3 has-focus-visible:outline-trb11-star"><input type="radio" name="trb-11" value="5" class="sr-only"><svg viewBox="0 0 24 24" aria-hidden="true" class="size-[38px] fill-transparent stroke-trb11-off stroke-[1.6] transition-[fill,stroke,scale] duration-250 [--d:240ms] group-has-checked/star:animate-trb11-pop group-has-checked/star:fill-trb11-star group-has-checked/star:stroke-trb11-star group-has-[~label_input:checked]/star:animate-trb11-pop group-has-[~label_input:checked]/star:fill-trb11-star group-has-[~label_input:checked]/star:stroke-trb11-star group-hover/stars:fill-transparent! group-hover/stars:stroke-trb11-off! [label:hover_&]:scale-110 [label:hover_&]:fill-trb11-star/55! [label:hover_&]:stroke-trb11-star! [label:has(~label:hover)_&]:fill-trb11-star/55! [label:has(~label:hover)_&]:stroke-trb11-star! motion-reduce:animate-none! motion-reduce:scale-100!"><path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg><span class="sr-only">5 of 5 stars</span></label>
</div>
<p aria-hidden="true" class="min-h-7 text-center text-lg font-bold text-trb11-ink">
<span class="hidden group-has-[[value='1']:checked]/rate:inline">Poor</span><span class="hidden group-has-[[value='2']:checked]/rate:inline">Fair</span><span class="hidden group-has-[[value='3']:checked]/rate:inline">Good</span><span class="hidden group-has-[[value='4']:checked]/rate:inline">Great</span><span class="hidden group-has-[[value='5']:checked]/rate:inline">Loved it</span>
</p>
<p class="text-[13px] text-trb11-mut">Hover to preview · click or use arrow keys to rate</p>
</fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-trb11-bg: #fdf9f0;
--color-trb11-bg: oklch(0.982 0.012 85);
--color-trb11-ink: #292317;
--color-trb11-ink: oklch(0.26 0.02 80);
--color-trb11-mut: #6f6446;
--color-trb11-mut: oklch(0.5 0.04 85);
--color-trb11-line: #efe6d0;
--color-trb11-line: oklch(0.925 0.025 85);
--color-trb11-off: #b8ab88;
--color-trb11-off: oklch(0.74 0.045 88);
--color-trb11-star: #f2b320;
--color-trb11-star: oklch(0.8 0.16 80);
--shadow-trb11-card: 0 18px 44px -24px oklch(0.4 0.06 80 / 0.35);
--font-trb11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
/* inline: the pop token reads each star's own --d delay. */
@theme inline {
--animate-trb11-pop: trb11-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) var(--d, 0ms) backwards;
}
@keyframes trb11-pop {
0% {
scale: 0.6;
}
60% {
scale: 1.25;
}
100% {
scale: 1;
}
}
/* 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-11 {
width: 100%;
min-height: 100svh;
}
```How this works
Three tiers of state. Checked fill is a normal variant; the "clear everything while hovering" reset is group-hover/stars:…!; the preview trail is an arbitrary variant — label:hover .star — which is both important and more specific, so it beats the reset.
Stagger without four keyframes. The pop token is declared in @theme inline with var(--d,0ms) as its delay, so each star sets [--d:60ms], [--d:120ms]… and the one animation sweeps left to right.
Make it yours
- Gold:
--color-trb11-star. - Sweep speed: the 60ms step in each star's
[--d:…]. - Swap the star path for hearts — the logic doesn't change.
Gotchas — read before shipping
- Keep DOM order 1→5 — screen readers and arrow keys follow it; the
:has(~)trick exists so you don't reverse it. - Every star needs
sr-onlytext; a glyph alone is not a label. - v4's important modifier is a suffix:
fill-x!.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Cumulative fill depends on :has(); below it only the picked star fills, and the rating still submits.