18 examples Responsive intermediate

18 CSS Radio Button Designs

A CSS radio button is a styled mutually-exclusive form control — pick one option from a set. These 18 hand-coded designs cover every common interaction style: classic concentric pulse, sliding pill toggles, magnetic orbs that glide between options, ink-drop fills, star bursts, tilt cards, segmented bars, neon rings, pricing tiers with featured ribbons, and glow pulses. Every demo wraps a real native input type=radio (kept visually hidden but focusable) with a custom indicator, so keyboard, screen-reader, and form-submission semantics all work out of the box. 100% pure CSS — no JavaScript required.

18 hand-coded CSS radio buttons — pill slider, magnetic orb, ink drop, star burst, segmented bar, neon ring, pricing tier, glow pulse and more. Every demo is unique, accessible, and copy-paste ready.

18 unique radios 18 Pure CSS WCAG-friendly customisable
01 / 18
Concentric Pulse
Pure CSS
Plan
Classic outer ring with inner dot — on check the dot bursts outward then settles into place. The benchmark every radio is measured against.
.crb-pulse { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-pulse label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-pulse input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-pulse span {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid #444461; background: transparent;
  position: relative; transition: border-color 0.2s;
}
.crb-pulse span::after {
  content: ''; position: absolute; inset: 3px;
  border-radius: 50%; background: #7c6cff;
  transform: scale(0); transition: transform 0.4s cubic-bezier(0.34,1.56,0.64,1);
}
.crb-pulse input:checked + span { border-color: #7c6cff; }
.crb-pulse input:checked + span::after { transform: scale(1); animation: crb-pulse-burst 0.5s ease-out; }
@keyframes crb-pulse-burst {
  0%   { box-shadow: 0 0 0 0   rgba(124,108,255,0.6); }
  100% { box-shadow: 0 0 0 12px rgba(124,108,255,0); }
}
<fieldset class="crb-pulse">
  <legend class="crb-sr">Plan</legend>
  <label><input type="radio" name="crb-pulse" checked /><span></span>Starter</label>
  <label><input type="radio" name="crb-pulse" /><span></span>Pro</label>
  <label><input type="radio" name="crb-pulse" /><span></span>Team</label>
</fieldset>
02 / 18
Pill Slider
Pure CSS
Billing
Three-option pill where the active background slides smoothly between selections — perfect for plan toggles and view switchers.
.crb-pill {
  display: inline-flex; position: relative; padding: 4px;
  background: #1a1a28; border: 1px solid rgba(255,255,255,0.08);
  border-radius: 999px;
}
.crb-pill input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-pill label {
  flex: 1 1 0; min-width: 80px;
  position: relative; z-index: 1;
  padding: 8px 16px;
  font: 600 12px/1 system-ui, sans-serif; color: #9d9bbf;
  text-align: center;
  cursor: pointer; transition: color 0.25s;
  border-radius: 999px;
}
.crb-pill input:checked + label { color: #fff; }
.crb-pill-thumb {
  position: absolute; top: 4px; bottom: 4px; left: 4px;
  width: calc(33.333% - 2.667px);
  background: linear-gradient(135deg, #7c6cff, #a78bfa);
  border-radius: 999px; z-index: 0;
  transition: transform 0.4s cubic-bezier(0.65,0,0.35,1);
}
#crb-pill-2:checked ~ .crb-pill-thumb { transform: translateX(100%); }
#crb-pill-3:checked ~ .crb-pill-thumb { transform: translateX(200%); }
<fieldset class="crb-pill">
  <legend class="crb-sr">Billing</legend>
  <input type="radio" name="crb-pill" id="crb-pill-1" checked />
  <label for="crb-pill-1">Monthly</label>
  <input type="radio" name="crb-pill" id="crb-pill-2" />
  <label for="crb-pill-2">Yearly</label>
  <input type="radio" name="crb-pill" id="crb-pill-3" />
  <label for="crb-pill-3">Forever</label>
  <span class="crb-pill-thumb"></span>
</fieldset>
03 / 18
Magnetic Orb
Pure CSS
Speed
A single violet orb slides between options as if magnetically pulled — uses sibling-position state to drive the orb position with zero JS.
.crb-orb { display: flex; flex-direction: column; gap: 8px; border: 0; padding: 0; }
.crb-orb input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-orb label {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 14px; border-radius: 8px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
  transition: background 0.25s, color 0.25s;
}
.crb-orb-dot {
  width: 14px; height: 14px; border-radius: 50%;
  background: #2a2a3e; box-shadow: inset 0 0 0 2px #444461;
  transition: background 0.35s cubic-bezier(0.65,0,0.35,1), box-shadow 0.35s, transform 0.35s;
}
.crb-orb input:checked + label { background: rgba(124,108,255,0.08); color: #fff; }
.crb-orb input:checked + label .crb-orb-dot {
  background: #7c6cff; box-shadow: inset 0 0 0 2px #7c6cff, 0 0 12px rgba(124,108,255,0.7);
  transform: scale(1.15);
}
<fieldset class="crb-orb">
  <legend class="crb-sr">Speed</legend>
  <input type="radio" name="crb-orb" id="crb-orb-1" checked />
  <label for="crb-orb-1"><span class="crb-orb-dot"></span>Slow</label>
  <input type="radio" name="crb-orb" id="crb-orb-2" />
  <label for="crb-orb-2"><span class="crb-orb-dot"></span>Fast</label>
  <input type="radio" name="crb-orb" id="crb-orb-3" />
  <label for="crb-orb-3"><span class="crb-orb-dot"></span>Turbo</label>
</fieldset>
04 / 18
Ink Drop
Pure CSS
Theme
A drop of ink falls from above and fills the circle on selection — uses a clip-path to animate the liquid level from empty to full.
.crb-ink { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-ink label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-ink input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-ink span {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid #444461; background: transparent;
  position: relative; overflow: hidden;
  transition: border-color 0.25s;
}
.crb-ink span::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, #06b6d4, #0ea5e9);
  transform: translateY(-100%);
  transition: transform 0.5s cubic-bezier(0.65,0,0.35,1);
}
.crb-ink input:checked + span { border-color: #06b6d4; }
.crb-ink input:checked + span::before { transform: translateY(0); }
<fieldset class="crb-ink">
  <legend class="crb-sr">Theme</legend>
  <label><input type="radio" name="crb-ink" /><span></span>Light</label>
  <label><input type="radio" name="crb-ink" checked /><span></span>Dark</label>
  <label><input type="radio" name="crb-ink" /><span></span>Auto</label>
</fieldset>
05 / 18
Star Burst
Pure CSS
Priority
A 5-point star draws itself in via stroke-dasharray when selected — premium choice for ratings, favourites, and priority radios.
.crb-star { display: flex; flex-direction: column; gap: 8px; border: 0; padding: 0; }
.crb-star label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-star input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-star svg {
  width: 22px; height: 22px;
  fill: transparent;
  stroke: #444461; stroke-width: 1.5;
  transition: stroke 0.3s, fill 0.4s;
}
.crb-star input:checked + svg {
  stroke: #f5a623; fill: #f5a623;
  filter: drop-shadow(0 0 6px rgba(245,166,35,0.5));
  animation: crb-star-draw 0.6s ease-out;
}
@keyframes crb-star-draw {
  0%   { stroke-dasharray: 60; stroke-dashoffset: 60; fill: transparent; }
  60%  { stroke-dasharray: 60; stroke-dashoffset: 0;  fill: transparent; }
  100% { stroke-dasharray: 60; stroke-dashoffset: 0;  fill: #f5a623; }
}
<fieldset class="crb-star">
  <legend class="crb-sr">Priority</legend>
  <label><input type="radio" name="crb-star" /><svg viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15,9 22,9 17,14 19,21 12,17 5,21 7,14 2,9 9,9"/></svg>Low</label>
  <label><input type="radio" name="crb-star" checked /><svg viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15,9 22,9 17,14 19,21 12,17 5,21 7,14 2,9 9,9"/></svg>Medium</label>
  <label><input type="radio" name="crb-star" /><svg viewBox="0 0 24 24" aria-hidden="true"><polygon points="12,2 15,9 22,9 17,14 19,21 12,17 5,21 7,14 2,9 9,9"/></svg>High</label>
</fieldset>
06 / 18
Tilt Card
Pure CSS
Choose plan
Each option is a card that tilts up and glows when chosen — feature-rich plan picker for SaaS pricing pages.
.crb-tilt { display: flex; gap: 8px; border: 0; padding: 0; }
.crb-tilt input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-tilt label {
  flex: 1; display: flex; flex-direction: column; gap: 4px;
  padding: 12px 10px; border-radius: 10px;
  background: #1a1a28; border: 1px solid rgba(255,255,255,0.06);
  cursor: pointer;
  transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.3s, border-color 0.3s, background 0.3s;
}
.crb-tilt label strong {
  font: 700 13px/1 system-ui, sans-serif; color: #fff;
}
.crb-tilt label em {
  font: 500 11px/1 monospace; color: #9d9bbf; font-style: normal;
}
.crb-tilt label:has(input:checked) {
  transform: translateY(-4px) rotate(-1deg);
  background: linear-gradient(135deg, rgba(124,108,255,0.18), rgba(167,139,250,0.1));
  border-color: rgba(124,108,255,0.5);
  box-shadow: 0 8px 24px rgba(124,108,255,0.3);
}
<fieldset class="crb-tilt">
  <legend class="crb-sr">Choose plan</legend>
  <label><input type="radio" name="crb-tilt" /><strong>Solo</strong><em>$9/mo</em></label>
  <label><input type="radio" name="crb-tilt" checked /><strong>Team</strong><em>$29/mo</em></label>
  <label><input type="radio" name="crb-tilt" /><strong>Scale</strong><em>$99/mo</em></label>
</fieldset>
07 / 18
Minimal Tick
Pure CSS
Layout
Clean radio that converts to a check tick on selection — instead of a dot, the indicator becomes a checkmark for a "confirmed" feel.
.crb-tick { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-tick label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-tick input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-tick span {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid #444461; background: transparent;
  position: relative;
  transition: border-color 0.2s, background 0.3s;
}
.crb-tick span::after {
  content: ''; position: absolute;
  top: 50%; left: 50%;
  width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: translate(-50%, -60%) rotate(45deg) scale(0);
  transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
}
.crb-tick input:checked + span { background: #2ecc8a; border-color: #2ecc8a; }
.crb-tick input:checked + span::after { transform: translate(-50%, -60%) rotate(45deg) scale(1); }
<fieldset class="crb-tick">
  <legend class="crb-sr">Layout</legend>
  <label><input type="radio" name="crb-tick" checked /><span></span>Compact</label>
  <label><input type="radio" name="crb-tick" /><span></span>Comfortable</label>
  <label><input type="radio" name="crb-tick" /><span></span>Spacious</label>
</fieldset>
08 / 18
Color Swatches
Pure CSS
Colour
Big circular colour swatch radios — for theme pickers and palette selectors. The selected swatch shows a contrasting inner ring.
.crb-swatch { display: flex; gap: 10px; border: 0; padding: 0; }
.crb-swatch input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-swatch label { cursor: pointer; }
.crb-swatch span {
  display: block;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--c);
  box-shadow: inset 0 0 0 0 #15151d, 0 0 0 0 var(--c);
  transition: box-shadow 0.3s, transform 0.25s cubic-bezier(0.34,1.56,0.64,1);
}
.crb-swatch label:hover span { transform: scale(1.08); }
.crb-swatch input:checked + span {
  box-shadow: inset 0 0 0 4px #15151d, 0 0 0 2px var(--c), 0 0 16px var(--c);
  transform: scale(1.12);
}
<fieldset class="crb-swatch">
  <legend class="crb-sr">Colour</legend>
  <label style="--c:#7c6cff"><input type="radio" name="crb-swatch" /><span></span></label>
  <label style="--c:#ff6c8a"><input type="radio" name="crb-swatch" checked /><span></span></label>
  <label style="--c:#2ecc8a"><input type="radio" name="crb-swatch" /><span></span></label>
  <label style="--c:#f5a623"><input type="radio" name="crb-swatch" /><span></span></label>
  <label style="--c:#06b6d4"><input type="radio" name="crb-swatch" /><span></span></label>
</fieldset>
09 / 18
Segmented Bar
Pure CSS
View
iOS-style segmented control with sliding pill background — sibling-position selectors drive the pill via translateX, no JS required.
.crb-seg {
  display: inline-flex; position: relative;
  padding: 3px; background: #0c0c12;
  border: 1px solid rgba(255,255,255,0.06); border-radius: 8px;
}
.crb-seg input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-seg label {
  flex: 1 1 0; min-width: 64px;
  position: relative; z-index: 1;
  padding: 7px 14px;
  font: 600 12px/1 system-ui, sans-serif; color: #9d9bbf;
  text-align: center;
  cursor: pointer; transition: color 0.25s;
  border-radius: 6px;
}
.crb-seg input:checked + label { color: #fff; }
.crb-seg-thumb {
  position: absolute; top: 3px; bottom: 3px; left: 3px;
  width: calc(25% - 1.5px);
  background: #1f1f2e; border: 1px solid rgba(124,108,255,0.4);
  border-radius: 6px; z-index: 0;
  box-shadow: 0 2px 8px rgba(0,0,0,0.4);
  transition: transform 0.35s cubic-bezier(0.65,0,0.35,1);
}
#crb-seg-2:checked ~ .crb-seg-thumb { transform: translateX(100%); }
#crb-seg-3:checked ~ .crb-seg-thumb { transform: translateX(200%); }
#crb-seg-4:checked ~ .crb-seg-thumb { transform: translateX(300%); }
<fieldset class="crb-seg">
  <legend class="crb-sr">View</legend>
  <input type="radio" name="crb-seg" id="crb-seg-1" checked />
  <label for="crb-seg-1">Day</label>
  <input type="radio" name="crb-seg" id="crb-seg-2" />
  <label for="crb-seg-2">Week</label>
  <input type="radio" name="crb-seg" id="crb-seg-3" />
  <label for="crb-seg-3">Month</label>
  <input type="radio" name="crb-seg" id="crb-seg-4" />
  <label for="crb-seg-4">Year</label>
  <span class="crb-seg-thumb"></span>
</fieldset>
10 / 18
Neon Ring
Pure CSS
Mode
Outline ring lights up neon-green with a halo glow on check — built for dark dashboards and developer tools.
.crb-neon { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-neon label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 monospace; color: #9d9bbf; cursor: pointer;
}
.crb-neon input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-neon span {
  width: 16px; height: 16px; border-radius: 50%;
  border: 1.5px solid #2a4a3a; background: transparent;
  position: relative;
  transition: border-color 0.25s, box-shadow 0.3s;
}
.crb-neon span::after {
  content: ''; position: absolute; inset: 3px;
  border-radius: 50%; background: #2ecc8a;
  opacity: 0; transform: scale(0.4);
  transition: opacity 0.25s, transform 0.3s;
}
.crb-neon input:checked + span {
  border-color: #2ecc8a;
  box-shadow: 0 0 12px rgba(46,204,138,0.6), inset 0 0 6px rgba(46,204,138,0.3);
}
.crb-neon input:checked + span::after {
  opacity: 1; transform: scale(1);
  box-shadow: 0 0 6px #2ecc8a;
}
<fieldset class="crb-neon">
  <legend class="crb-sr">Mode</legend>
  <label><input type="radio" name="crb-neon" /><span></span>Idle</label>
  <label><input type="radio" name="crb-neon" checked /><span></span>Active</label>
  <label><input type="radio" name="crb-neon" /><span></span>Burst</label>
</fieldset>
11 / 18
Pricing Tier
Pure CSS
Plan tier
Pricing-card style with a "Best Value" featured ribbon revealed on selection — production-ready for SaaS plan pickers.
.crb-tier { display: flex; gap: 10px; border: 0; padding: 14px 0 0; }
.crb-tier input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-tier label { flex: 1; cursor: pointer; }
.crb-tier-card {
  display: flex; flex-direction: column; gap: 4px; align-items: flex-start;
  position: relative;
  padding: 12px;
  background: #1a1a28; border: 1.5px solid rgba(255,255,255,0.06); border-radius: 10px;
  transition: border-color 0.3s, background 0.3s;
}
.crb-tier-card strong { font: 700 14px/1 system-ui, sans-serif; color: #fff; }
.crb-tier-card em { font: 600 12px/1 monospace; color: #a78bfa; font-style: normal; }
.crb-tier-ribbon {
  position: absolute;
  top: 0; left: 50%;
  padding: 4px 12px;
  border-radius: 999px;
  font: 700 9px/1 system-ui, sans-serif;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  background: linear-gradient(90deg, #7c6cff, #ff6c8a); color: #fff;
  box-shadow: 0 2px 8px rgba(124,108,255,0.4);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.6);
  transition: opacity 0.25s, transform 0.35s cubic-bezier(0.34,1.56,0.64,1);
  pointer-events: none;
}
.crb-tier input:checked + .crb-tier-card {
  border-color: #7c6cff;
  background: linear-gradient(135deg, rgba(124,108,255,0.15), rgba(255,108,138,0.05));
}
.crb-tier input:checked + .crb-tier-card .crb-tier-ribbon {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
<fieldset class="crb-tier">
  <legend class="crb-sr">Plan tier</legend>
  <label>
    <input type="radio" name="crb-tier" />
    <span class="crb-tier-card">
      <strong>Basic</strong>
      <em>$5/mo</em>
      <span class="crb-tier-ribbon">Best value</span>
    </span>
  </label>
  <label>
    <input type="radio" name="crb-tier" checked />
    <span class="crb-tier-card">
      <strong>Plus</strong>
      <em>$15/mo</em>
      <span class="crb-tier-ribbon">Best value</span>
    </span>
  </label>
</fieldset>
12 / 18
Wave Fill
Pure CSS
Volume
Selected radio fills with a sine-wave water animation — uses a moving SVG-free CSS wave for an organic liquid feel.
.crb-wave { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-wave label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-wave input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-wave span {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid #444461;
  position: relative; overflow: hidden;
  background: transparent;
  transition: border-color 0.25s;
}
.crb-wave span::before {
  content: ''; position: absolute;
  top: 100%; left: -50%; width: 200%; height: 200%;
  background: radial-gradient(ellipse at top, transparent 40%, #06b6d4 41%);
  transition: top 0.5s ease-out;
}
.crb-wave input:checked + span { border-color: #06b6d4; }
.crb-wave input:checked + span::before {
  top: -10%;
  animation: crb-wave-bob 1.4s ease-in-out infinite;
}
@keyframes crb-wave-bob {
  0%,100% { transform: translateY(0); }
  50%      { transform: translateY(-2px); }
}
<fieldset class="crb-wave">
  <legend class="crb-sr">Volume</legend>
  <label><input type="radio" name="crb-wave" /><span></span>Low</label>
  <label><input type="radio" name="crb-wave" checked /><span></span>Mid</label>
  <label><input type="radio" name="crb-wave" /><span></span>Loud</label>
</fieldset>
13 / 18
Clip Mask
Pure CSS
Speed
The fill is revealed via an animated clip-path morph from a tiny triangle to a full circle — a unique geometric reveal.
.crb-clip { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-clip label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 monospace; color: #cbd5e1; cursor: pointer;
}
.crb-clip input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-clip span {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid #444461;
  position: relative; overflow: hidden;
  transition: border-color 0.25s;
}
.crb-clip span::after {
  content: ''; position: absolute; inset: 3px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff6c8a, #f5a623);
  clip-path: polygon(50% 50%, 50% 50%, 50% 50%);
  transition: clip-path 0.5s cubic-bezier(0.65,0,0.35,1);
}
.crb-clip input:checked + span { border-color: #ff6c8a; }
.crb-clip input:checked + span::after {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
<fieldset class="crb-clip">
  <legend class="crb-sr">Speed</legend>
  <label><input type="radio" name="crb-clip" /><span></span>0.5x</label>
  <label><input type="radio" name="crb-clip" /><span></span>1x</label>
  <label><input type="radio" name="crb-clip" checked /><span></span>2x</label>
</fieldset>
14 / 18
Gradient Border
Pure CSS
Tier
Conic gradient border traces around the selected option — creates a slowly rotating rainbow ring on focus.
.crb-grad { display: flex; gap: 8px; border: 0; padding: 0; }
.crb-grad input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-grad label { cursor: pointer; }
.crb-grad-card {
  display: block; padding: 10px 18px;
  background: #1a1a28; border-radius: 8px;
  font: 600 12px/1 system-ui, sans-serif; color: #9d9bbf;
  position: relative; isolation: isolate;
  transition: color 0.25s;
}
.crb-grad-card::before {
  content: ''; position: absolute; inset: -2px; border-radius: 10px;
  background: conic-gradient(from 0deg, #7c6cff, #ff6c8a, #f5a623, #2ecc8a, #06b6d4, #7c6cff);
  z-index: -1; opacity: 0;
  animation: crb-grad-spin 4s linear infinite;
  transition: opacity 0.3s;
}
.crb-grad-card::after {
  content: ''; position: absolute; inset: 0; border-radius: 8px;
  background: #1a1a28; z-index: -1;
}
.crb-grad input:checked + .crb-grad-card { color: #fff; }
.crb-grad input:checked + .crb-grad-card::before { opacity: 1; }
@keyframes crb-grad-spin { to { transform: rotate(360deg); } }
<fieldset class="crb-grad">
  <legend class="crb-sr">Tier</legend>
  <label><input type="radio" name="crb-grad" /><span class="crb-grad-card">Bronze</span></label>
  <label><input type="radio" name="crb-grad" /><span class="crb-grad-card">Silver</span></label>
  <label><input type="radio" name="crb-grad" checked /><span class="crb-grad-card">Gold</span></label>
</fieldset>
15 / 18
Avatar Picker
Pure CSS
Avatar
Round avatar tiles that scale up and glow on selection — perfect for character/profile pickers in onboarding flows.
.crb-avatar { display: flex; gap: 10px; border: 0; padding: 0; }
.crb-avatar input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-avatar label { cursor: pointer; }
.crb-avatar span {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%;
  background: linear-gradient(135deg, var(--c), color-mix(in srgb, var(--c), white 30%));
  font: 700 14px/1 system-ui, sans-serif; color: #fff;
  box-shadow: 0 0 0 0 var(--c);
  transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.3s, opacity 0.25s;
  opacity: 0.55;
}
.crb-avatar label:hover span { opacity: 0.85; }
.crb-avatar input:checked + span {
  opacity: 1; transform: scale(1.15);
  box-shadow: 0 0 0 3px #15151d, 0 0 0 5px var(--c), 0 0 18px var(--c);
}
<fieldset class="crb-avatar">
  <legend class="crb-sr">Avatar</legend>
  <label style="--c:#7c6cff"><input type="radio" name="crb-avatar" /><span>A</span></label>
  <label style="--c:#ff6c8a"><input type="radio" name="crb-avatar" checked /><span>B</span></label>
  <label style="--c:#2ecc8a"><input type="radio" name="crb-avatar" /><span>C</span></label>
  <label style="--c:#f5a623"><input type="radio" name="crb-avatar" /><span>D</span></label>
</fieldset>
16 / 18
Vertical Stack
Pure CSS
Notifications
Vertical stack with a slim left accent rail that slides between options — perfect for settings panels and form sections.
.crb-stack {
  display: flex; flex-direction: column;
  position: relative; padding-left: 12px;
  border: 0; border-left: 2px solid rgba(255,255,255,0.06);
}
.crb-stack input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-stack label {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px; border-radius: 6px;
  cursor: pointer; transition: color 0.25s, background 0.25s;
}
.crb-stack label strong {
  font: 600 13px/1 system-ui, sans-serif; color: #cbd5e1;
}
.crb-stack label em {
  font: 500 11px/1 system-ui, sans-serif; color: #6b6987; font-style: normal;
}
.crb-stack input:checked + label strong { color: #fff; }
.crb-stack input:checked + label { background: rgba(124,108,255,0.06); }
.crb-stack-rail {
  position: absolute; left: -2px; top: 0;
  width: 2px; height: 33.333%;
  background: linear-gradient(180deg, #7c6cff, #a78bfa);
  border-radius: 2px;
  transition: transform 0.4s cubic-bezier(0.65,0,0.35,1);
}
#crb-stack-2:checked ~ .crb-stack-rail { transform: translateY(100%); }
#crb-stack-3:checked ~ .crb-stack-rail { transform: translateY(200%); }
<fieldset class="crb-stack">
  <legend class="crb-sr">Notifications</legend>
  <input type="radio" name="crb-stack" id="crb-stack-1" checked />
  <label for="crb-stack-1"><strong>All</strong><em>Every update</em></label>
  <input type="radio" name="crb-stack" id="crb-stack-2" />
  <label for="crb-stack-2"><strong>Important</strong><em>Critical only</em></label>
  <input type="radio" name="crb-stack" id="crb-stack-3" />
  <label for="crb-stack-3"><strong>Off</strong><em>Silent mode</em></label>
  <span class="crb-stack-rail"></span>
</fieldset>
17 / 18
3D Push
Pure CSS
Difficulty
Skeuomorphic depressed-button style — the selected option sinks into the surface with an inverted shadow for tactile feedback.
.crb-push { display: flex; gap: 8px; border: 0; padding: 0; }
.crb-push input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-push label { cursor: pointer; }
.crb-push span {
  display: block; padding: 10px 18px;
  background: linear-gradient(180deg, #2a2a3e, #1a1a28);
  color: #cbd5e1; font: 600 12px/1 system-ui, sans-serif;
  border-radius: 8px;
  box-shadow: 0 3px 0 #0c0c12, inset 0 1px 0 rgba(255,255,255,0.06);
  transform: translateY(0);
  transition: transform 0.12s, box-shadow 0.12s, color 0.2s, background 0.25s;
}
.crb-push input:checked + span {
  transform: translateY(3px);
  background: linear-gradient(180deg, #1a1a28, #0c0c12);
  color: #a78bfa;
  box-shadow: 0 0 0 #0c0c12, inset 0 2px 6px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(124,108,255,0.3);
}
<fieldset class="crb-push">
  <legend class="crb-sr">Difficulty</legend>
  <label><input type="radio" name="crb-push" /><span>Easy</span></label>
  <label><input type="radio" name="crb-push" checked /><span>Hard</span></label>
  <label><input type="radio" name="crb-push" /><span>Expert</span></label>
</fieldset>
18 / 18
Glow Pulse
Pure CSS
Reaction
Selecting a radio fires a soft expanding halo that radiates outward from the dot — pure CSS, animation runs once per selection via a keyframed box-shadow ripple.
.crb-glow { display: flex; flex-direction: column; gap: 10px; border: 0; padding: 0; }
.crb-glow label {
  display: flex; align-items: center; gap: 10px;
  font: 500 13px/1 system-ui, sans-serif; color: #cbd5e1; cursor: pointer;
}
.crb-glow input { appearance: none; -webkit-appearance: none; position: absolute; opacity: 0; }
.crb-glow span {
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid #444461; background: transparent;
  position: relative;
  transition: border-color 0.2s, background 0.25s;
}
.crb-glow span::after {
  content: ''; position: absolute; inset: -2px;
  border-radius: 50%;
  box-shadow: 0 0 0 0 rgba(255,108,138,0.7);
  pointer-events: none;
}
.crb-glow input:checked + span {
  background: linear-gradient(135deg, #ff6c8a, #a78bfa);
  border-color: transparent;
}
.crb-glow input:checked + span::after {
  animation: crb-glow-ripple 0.9s ease-out;
}
@keyframes crb-glow-ripple {
  0%   { box-shadow: 0 0 0 0   rgba(255,108,138,0.7), 0 0 0 0  rgba(167,139,250,0.5); }
  60%  { box-shadow: 0 0 0 14px rgba(255,108,138,0),   0 0 0 22px rgba(167,139,250,0); }
  100% { box-shadow: 0 0 0 14px rgba(255,108,138,0),   0 0 0 22px rgba(167,139,250,0); }
}
<fieldset class="crb-glow">
  <legend class="crb-sr">Reaction</legend>
  <label><input type="radio" name="crb-glow" /><span></span>Like</label>
  <label><input type="radio" name="crb-glow" /><span></span>Love</label>
  <label><input type="radio" name="crb-glow" /><span></span>Celebrate</label>
</fieldset>
FAQ

Frequently asked questions

How do I style a radio button with CSS?
Visually hide the native input type=radio (using a class with position:absolute / opacity:0, NOT display:none — that breaks keyboard focus), then style the adjacent label or a sibling span as the custom indicator. Use the :checked pseudo-class on the input plus an adjacent-sibling selector (input:checked + label) to switch the indicator state.
Are these custom radio buttons accessible?
Yes. Each demo uses a real native input type=radio that remains keyboard-focusable and screen-reader-announced. Custom visuals are pure presentation; semantics, form submission, name-grouping for mutual exclusivity, and arrow-key navigation all work natively.
Do I need JavaScript to make these radio buttons work?
No — all 18 demos are 100% pure CSS. State machines run on :checked plus sibling selectors and :has(). Animations use pure CSS keyframes. The only JS in the gallery is for tab switching and copy-to-clipboard on the code panel.
What's the difference between a radio button and a checkbox?
Radio buttons are mutually exclusive within a name group — picking one auto-deselects the others (one of many). Checkboxes are independent — any combination can be checked at once (any of many). Use radios for plan tiers, payment methods, single-answer questions; checkboxes for filters, multi-select tags, and consent boxes.
Can I use these in any framework?
Yes. Plain HTML and CSS, no dependencies, no build step. Drop the markup into React (with className), Vue, Svelte, Astro or static HTML — the styles work as-is. MIT licensed.

Related collections