01. Tailwind Radio Button Cards (Selectable Cards) (opens in a new tab)
has-checked: variant, descendants follow with group-has-checked:, and the lift is gated behind motion-safe: instead of being undone later.24 Tailwind CSS radio buttons for checkout and onboarding flows, pricing-plan pickers, settings option lists, e-commerce colour and image swatches, star ratings, and payment-method selection — spanning selectable cards, segmented controls and sliding pills, 3D orbs and tilt cards, ink-splash and ripple animations, neon and glassmorphism styling, avatar pickers, and dark-mode themes. Scoped under .trb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related24 CSS Radio Buttons47 Tailwind CSS Toggles & Switches29 Tailwind CSS Checkboxes20 Tailwind CSS Custom Selects
Premium upgradePartner: Untitled UI
Affiliate linkBuilding a whole Tailwind app, not just this component?
You just browsed 24 Tailwind radio buttons. Untitled UI Pro unlocks 5,000+ components and sections and 250+ page examples for React, styled with Tailwind CSS v4. One licence is a one-time $349 with lifetime updates; teams start at $999.
Untitled UI React. We may earn a commission, at no extra cost to you.
has-checked: variant, descendants follow with group-has-checked:, and the lift is gated behind motion-safe: instead of being undone later.before: pseudo-element on the track, moved by one arbitrary variant: has-[label:nth-child(2)_input:checked]:before:translate-x-full. The price below re-renders from the same radio state.has-checked: on the row, and group-has-checked: on everything that responds — tint, tile, icon and dot.--animate-trb04-breathe theme token whose keyframes live inside @theme, and the solid inner dot keeps the checked state readable without glow (WCAG 1.4.1).<input type="radio"> itself with appearance-none. No hidden input, no wrapper spans, no ARIA — checked:, disabled: and focus-visible: sit directly on the element that owns those states, and forced-colors:appearance-auto hands the control back to Windows High Contrast.after: ring, chased 120ms later by a sibling ring driven with peer-checked:. Both are one-shot animate-* tokens, so re-checking replays the splash for free.has-[label:nth-child(2)_input:checked]:[--thumb:…] on the track, read by before:bg-(--thumb).[--c:oklch(…)], and the shading tokens use @theme inline so they can read it.before: circle, scaled 0 → 34 by has-checked: with an asymmetric transition, so ink soaks in slowly and dries fast.group-has-[~label_input:checked]/star:: every star before the checked one lights up.perspective-[900px] on the row, transform-3d on the card, and an arbitrary [transform:rotateX(var(--rx))…] that a small controller feeds. The checked card locks in an accent edge-light and a check badge.[stroke-dashoffset:28] → group-has-checked:[stroke-dashoffset:0], and each option now previews itself — the grotesk, serif and mono choices are set in the faces they name.ring-2 ring-offset-3 ring-offset-trb14-bg, the swatch edge is inset-ring, and the photo filter, caption and stock note all switch with group-has-[[value=…]:checked]/pdp:.after: on the bar moved by has-[label:nth-child(n)_input:checked]:after:translate-x-*.@property (the one piece that stays CSS); notches are [clip-path:polygon(…)], the ring cut is before:[mask:radial-gradient(…)].[grid-area:1/1] and cross-faded by group-has-checked:; the summary line under the table follows the value.translate raises the water, rotate rolls it — so the rise transitions while the roll loops, with no combined keyframe.rounded-[85px_85px_18px_18px] → group-has-checked:rounded-[26px] — border-radius interpolates natively.[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.ring-3 ring-trb21-acc ring-offset-4 ring-offset-trb21-bg; the sprung lift is motion-safe:. An "Add profile" tile shows how to mix an action into the row without joining the radio group.divide-y on the container, the rail is scale-y-0 group-has-checked:scale-y-100, and each row now carries a card-brand chip.shadow-trb23-up, -hover, -down) pair with translate utilities, and group-active: previews the travel mid-click.[--c:oklch(…)]; the second ring is offset with [animation-delay:2s]; and a live presence preview at the top shows how you'll appear.<input type="radio"> in the DOM and strips its default rendering with appearance-none. You then draw the indicator with utilities on the input itself — a rounded-full ring plus a checked: variant that fills the centre dot or recolours the border. Because the element is still a genuine radio, the browser handles grouping, focus and form submission for free; nothing is faked with a <div>. The card demos push this further: the input is visually hidden with sr-only (still focusable) while a sibling label paints the whole card, reacting through peer-checked: or has-checked:. The rule across all 24 is the same — never delete the input, never swap it for a scripted <span>. Style the control that already exists.w-1/2 or w-1/3). Selection is read with the sibling peer-checked: variant or a parent :has() query, and the thumb is moved with translate-x-0, translate-x-full and friends, eased by transition-transform. Demo 08 also swaps the thumb colour per option as it lands. The detail that trips people up: the thumb must sit under the labels in stacking order while the text stays above it, and every segment must be equal width or the translate distances stop lining up. Because the state lives in the checked radio, a reload or submit preserves the choice.name. Radios only behave as a mutually-exclusive group when every <input type="radio"> in the group shares the same name value — the browser then unchecks the previous one for you. Copy a demo row and forget to reuse the name, or give each row a unique name, and every option becomes independently checkable, which is checkbox behaviour. The second offender is duplicated id attributes: each input needs a unique id and its label a matching for, or clicking a label toggles the wrong input. So per group: one shared name, a unique id per option, a unique for on each label. Remember the submitted data comes from value, so give every option a distinct, meaningful value too.Tab (focus lands on the checked option, or the first if none is checked) and you move between options with the arrow keys — the browser gives you that roving focus automatically, which a <div role="radiogroup"> reimplementation has to script by hand. Two things you must not break: keep the input focusable — use sr-only or appearance-none, never hidden or display:none, which drop it from the tab order — and render a visible focus-visible: ring on the control the user actually focuses. For a labelled set, wrap the options in a <fieldset> with a <legend> so screen readers announce the group's purpose. Icon-only pickers like the swatches and avatars need an aria-label on each input.peer pattern (Demos 01 and 17): mark the hidden input peer, put the card markup in the very next sibling label, and style it with peer-checked:border-indigo-500 peer-checked:ring-2. This only works when the label is a following sibling of the input — peer-* cannot look backwards or across branches. The newer has-checked: pattern uses a :has() wrapper: drop the input anywhere inside the label and style the label with has-checked:bg-slate-800, so the layout is free. Make the whole label clickable by giving it the padding, marking the input sr-only, and adding cursor-pointer. Do not bolt a separate click handler onto the card — the label already forwards the click to its input.1 to 5 DOM order and builds cumulative fill with the general-sibling combinator. Each star label styles itself and, through ~, the stars after it, so checking star 3 lights 1 through 3. Older CSS star widgets reversed the DOM with flex-row-reverse and used ~ the other way; this build keeps source order intact — correct for screen readers and keyboards — and drives the fill with :has() and peer logic instead. The hover preview is the same idea on hover, dimmed back once a real choice is made. The bug to avoid: if you reverse the visual order for styling convenience, arrow-key navigation and the submitted value come out backwards, so a keyboard user pressing right-arrow appears to lower their rating.<input type="checkbox">, not radios. Use a <select> when the list is long (roughly eight or more, or dynamically generated) so it saves vertical space and scrolls cleanly. And for a single on/off opt-in with a sensible default, one checkbox or a switch is clearer than a two-radio yes/no. Also avoid the heavier card and 3D demos for dense forms — a settings page with twenty questions does not want twenty glowing orbs. Match the ornament to the stakes: big selectable cards for a checkout decision, a plain row list for a preference buried in settings.transition and animate-* utilities on the checked state. Gate them behind Tailwind's motion-safe: and motion-reduce: variants so users who set reduce motion in their OS get the state change without the travel: put the effect on motion-safe:animate-ping and either drop it or swap to an instant colour change under motion-reduce:. Crucially, selection must never depend on the animation finishing — the checked state is already true the moment the input flips, so the option is chosen even with every keyframe disabled. Continuous effects like the pulsing glow are the most fatiguing, so silence those first under motion-reduce:.15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.
24 hand-coded CSS animated card patterns organised by what triggers the motion, not by card style. Scroll and entrance triggers: staggered grid reveals, @starting-style mount-in, scroll-driven scale and fade on animation-timeline: view(), View Transitions API card-to-detail morphs, blur-to-sharp lazy image loading, and FLIP re-layout when a filter changes. State-change triggers: add-to-cart success morph, like and save particle burst, expand and collapse to intrinsic height with calc-size(), swipe-to-dismiss on Pointer Events, and skeleton-to-loaded crossfade. Data-driven triggers: count-up KPI metrics, SVG sparkline draw-on-enter, and progress ring fill on load. Ambient idle loops: breathing glow, floating drift, animated gradient mesh backgrounds, conic rotating borders, auto-cycling testimonial decks, and live status pulses. Plus four hover patterns where the effect is the topic itself: cursor spotlight, holographic foil glare, corner ribbon slide, and horizontal accordion expand. Every card is scoped under a .ac-NN prefix for no-collision pasting, guards prefers-reduced-motion, animates compositor-only properties, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.