21 examples Responsive advanced

21 CSS Circular Menu Designs

A CSS circular menu is a navigation pattern where items are arranged around a circle, ring, dome, or orbit instead of a flat list. These 21 hand-coded designs span four themes — radial fan-outs, dome containers, orbital systems, and skeumorphic novelties — all 100% pure CSS with custom-property trigonometry. Every continuous animation honours prefers-reduced-motion.

21 free CSS circular menu designs across four themes — radial, dome, orbital and skeumorphic. Every demo uses semantic <a> or <label> elements with aria-label on icon-only items and respects prefers-reduced-motion.

21 unique menus 21 Pure CSS WCAG-friendly Mobile-first MIT licensed
Updated · 4 new demos added — Mission Hub, Service Orbit, Tactile Dial and Fan Reveal · May 2, 2026
01 / 21
Mission Hub
NEW Pure CSS
A 5-segment half-wheel command surface with curved SVG labels riding the outer rim, hairline wedge dividers, and a context-aware sub-toolbar that swaps icons based on the selected wedge. Pure CSS via :checked + :has(), teal-on-navy cockpit palette.
.ccm-sun {
  --n: 5;
  --ba: calc(180deg / var(--n));
  --r: 110px;
  position: relative;
  width: 240px;
  height: 130px;
  overflow: visible;
  font-family: system-ui, sans-serif;
}
.ccm-sun-wheel {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: calc(var(--r) * 2);
  height: var(--r);
  margin-left: calc(var(--r) * -1);
  border-radius: var(--r) var(--r) 0 0;
  background: radial-gradient(farthest-side at 50% 100%, #14b8a6 35%, #1e293b 36%, #1e293b calc(100% - 1px), transparent 100%);
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
}
.ccm-sun-rim {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 240px;
  height: 130px;
  margin-left: -120px;
  pointer-events: none;
  z-index: 1;
}
.ccm-sun-dividers line {
  stroke: rgba(94, 234, 212, 0.25);
  stroke-width: 1;
}
.ccm-sun-curve {
  font: 700 9px/1 ui-monospace, monospace;
  letter-spacing: 0.18em;
  fill: #cbd5e1;
}
.ccm-sun-pivot {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 70px;
  height: 35px;
  margin-left: -35px;
  border-radius: 35px 35px 0 0;
  background: radial-gradient(farthest-side at 50% 100%, #5eead4 0%, #14b8a6 70%, #0f766e 100%);
  box-shadow: 0 -2px 8px rgba(94, 234, 212, 0.35);
  z-index: 3;
}
.ccm-sun-arc {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 50px;
  height: var(--r);
  margin-left: -25px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 28px;
  cursor: pointer;
  transform-origin: 50% 100%;
  transform: rotate(calc((var(--i) + 0.5) * var(--ba) - 90deg));
  transition: color 0.25s;
  color: #94a3b8;
  z-index: 2;
}
.ccm-sun-ico {
  display: inline-block;
  font-size: 22px;
  line-height: 1;
  transform: rotate(calc((var(--i) + 0.5) * var(--ba) * -1 + 90deg));
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.ccm-sun-arc:hover,
.ccm-sun-arc:focus-visible {
  color: #5eead4;
}
.ccm-sun-arc:hover .ccm-sun-ico,
.ccm-sun-arc:focus-visible .ccm-sun-ico {
  transform: rotate(calc((var(--i) + 0.5) * var(--ba) * -1 + 90deg)) translateY(-3px) scale(1.1);
}
.ccm-sun:has(#ccm-sun-0:checked) [for="ccm-sun-0"],
.ccm-sun:has(#ccm-sun-1:checked) [for="ccm-sun-1"],
.ccm-sun:has(#ccm-sun-2:checked) [for="ccm-sun-2"],
.ccm-sun:has(#ccm-sun-3:checked) [for="ccm-sun-3"],
.ccm-sun:has(#ccm-sun-4:checked) [for="ccm-sun-4"] {
  color: #5eead4;
}
.ccm-sun:has(#ccm-sun-0:checked) [for="ccm-sun-0"] .ccm-sun-ico,
.ccm-sun:has(#ccm-sun-1:checked) [for="ccm-sun-1"] .ccm-sun-ico,
.ccm-sun:has(#ccm-sun-2:checked) [for="ccm-sun-2"] .ccm-sun-ico,
.ccm-sun:has(#ccm-sun-3:checked) [for="ccm-sun-3"] .ccm-sun-ico,
.ccm-sun:has(#ccm-sun-4:checked) [for="ccm-sun-4"] .ccm-sun-ico {
  filter: drop-shadow(0 0 6px rgba(94, 234, 212, 0.7));
}
.ccm-sun-bar {
  position: absolute;
  left: 50%;
  bottom: calc(var(--r) + 14px);
  display: flex;
  gap: 6px;
  padding: 8px 10px;
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 12px;
  transform: translateX(-50%) scale(0);
  transform-origin: 50% calc(100% + 10px);
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
  z-index: 4;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.ccm-sun-bar::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -7px;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: #1e293b;
}
/* Show only the sub-toolbar that matches the currently-checked radio */
.ccm-sun:has(#ccm-sun-0:checked) .ccm-sun-bar-0,
.ccm-sun:has(#ccm-sun-1:checked) .ccm-sun-bar-1,
.ccm-sun:has(#ccm-sun-2:checked) .ccm-sun-bar-2,
.ccm-sun:has(#ccm-sun-3:checked) .ccm-sun-bar-3,
.ccm-sun:has(#ccm-sun-4:checked) .ccm-sun-bar-4 {
  transform: translateX(-50%) scale(1);
}
.ccm-sun-sub {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(94, 234, 212, 0.12);
  color: #5eead4;
  font: 700 13px/1 ui-monospace, monospace;
  text-decoration: none;
  border: 1px solid rgba(94, 234, 212, 0.3);
  transition: background 0.2s, color 0.2s, transform 0.2s;
}
.ccm-sun-sub:hover,
.ccm-sun-sub:focus-visible {
  background: #5eead4;
  color: #0f172a;
  transform: scale(1.1);
}
<div class="ccm-sun" role="toolbar" aria-label="Mission Hub">
  <input type="radio" name="ccm-sun" id="ccm-sun-0" class="ccm-sun-r" hidden>
  <input type="radio" name="ccm-sun" id="ccm-sun-1" class="ccm-sun-r" hidden>
  <input type="radio" name="ccm-sun" id="ccm-sun-2" class="ccm-sun-r" hidden checked>
  <input type="radio" name="ccm-sun" id="ccm-sun-3" class="ccm-sun-r" hidden>
  <input type="radio" name="ccm-sun" id="ccm-sun-4" class="ccm-sun-r" hidden>
  <div class="ccm-sun-wheel" aria-hidden="true"></div>
  <svg class="ccm-sun-rim" viewBox="0 0 240 130" aria-hidden="true">
    <defs>
      <path id="ccm-sun-arc-path" d="M 30 130 A 90 90 0 0 1 210 130" fill="none"/>
    </defs>
    <g class="ccm-sun-dividers">
      <line x1="91.7" y1="109.4" x2="56.9" y2="84.1"/>
      <line x1="109.2" y1="96.7" x2="95.9" y2="55.8"/>
      <line x1="130.8" y1="96.7" x2="144.1" y2="55.8"/>
      <line x1="148.3" y1="109.4" x2="183.1" y2="84.1"/>
    </g>
    <text class="ccm-sun-curve">
      <textPath href="#ccm-sun-arc-path" startOffset="10%" text-anchor="middle">MUSIC</textPath>
      <textPath href="#ccm-sun-arc-path" startOffset="30%" text-anchor="middle">MAPS</textPath>
      <textPath href="#ccm-sun-arc-path" startOffset="50%" text-anchor="middle">COMPASS</textPath>
      <textPath href="#ccm-sun-arc-path" startOffset="70%" text-anchor="middle">CAMERA</textPath>
      <textPath href="#ccm-sun-arc-path" startOffset="90%" text-anchor="middle">SETTINGS</textPath>
    </text>
  </svg>
  <div class="ccm-sun-pivot" aria-hidden="true"></div>
  <label for="ccm-sun-0" class="ccm-sun-arc" style="--i:0" aria-label="Music"><span class="ccm-sun-ico">♪</span></label>
  <label for="ccm-sun-1" class="ccm-sun-arc" style="--i:1" aria-label="Maps"><span class="ccm-sun-ico">◈</span></label>
  <label for="ccm-sun-2" class="ccm-sun-arc" style="--i:2" aria-label="Compass"><span class="ccm-sun-ico">⌖</span></label>
  <label for="ccm-sun-3" class="ccm-sun-arc" style="--i:3" aria-label="Camera"><span class="ccm-sun-ico">◉</span></label>
  <label for="ccm-sun-4" class="ccm-sun-arc" style="--i:4" aria-label="Settings"><span class="ccm-sun-ico">⚙</span></label>
  <div class="ccm-sun-bar ccm-sun-bar-0" role="toolbar" aria-label="Music actions">
    <a href="#" class="ccm-sun-sub" aria-label="Play">▶</a>
    <a href="#" class="ccm-sun-sub" aria-label="Pause">❚❚</a>
    <a href="#" class="ccm-sun-sub" aria-label="Skip">⏭</a>
  </div>
  <div class="ccm-sun-bar ccm-sun-bar-1" role="toolbar" aria-label="Maps actions">
    <a href="#" class="ccm-sun-sub" aria-label="Search">⌕</a>
    <a href="#" class="ccm-sun-sub" aria-label="Pin">◉</a>
    <a href="#" class="ccm-sun-sub" aria-label="Route">➤</a>
  </div>
  <div class="ccm-sun-bar ccm-sun-bar-2" role="toolbar" aria-label="Compass actions">
    <a href="#" class="ccm-sun-sub" aria-label="Calibrate">⊙</a>
    <a href="#" class="ccm-sun-sub" aria-label="North">▲</a>
    <a href="#" class="ccm-sun-sub" aria-label="Lock">⏻</a>
  </div>
  <div class="ccm-sun-bar ccm-sun-bar-3" role="toolbar" aria-label="Camera actions">
    <a href="#" class="ccm-sun-sub" aria-label="Shutter">●</a>
    <a href="#" class="ccm-sun-sub" aria-label="Flash">⚡</a>
    <a href="#" class="ccm-sun-sub" aria-label="Flip">⤾</a>
  </div>
  <div class="ccm-sun-bar ccm-sun-bar-4" role="toolbar" aria-label="Settings actions">
    <a href="#" class="ccm-sun-sub" aria-label="Profile">☺</a>
    <a href="#" class="ccm-sun-sub" aria-label="Theme">◐</a>
    <a href="#" class="ccm-sun-sub" aria-label="Help">?</a>
  </div>
</div>
02 / 21
Service Orbit
NEW Pure CSS
Industries Validation Engineering Project Mgmt Manufacturing Automation Learn more →
Six service satellites orbit a central disc with an animated pointer that snaps to the active selection. Click any satellite to swap the centre title and rotate the pointer with a spring overshoot. Pure CSS via :checked + :has() — adapted from a service-business radial nav, simplified to remove its 600+ lines of icon paths and JS pointer animation.
.ccm-svc {
  --n: 6;
  --ba: calc(360deg / var(--n));
  --r: 105px;
  --pa: 0deg;
  position: relative;
  width: 260px;
  height: 260px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: system-ui, sans-serif;
}
.ccm-svc-orbit {
  position: absolute;
  width: calc(var(--r) * 2);
  height: calc(var(--r) * 2);
  border-radius: 50%;
  border: 1px dashed rgba(124, 108, 255, 0.18);
  background: radial-gradient(circle at center, rgba(124, 108, 255, 0.08), transparent 70%);
}
.ccm-svc-disc {
  position: relative;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7c6cff, #a78bfa);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px rgba(124, 108, 255, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2);
  z-index: 2;
}
.ccm-svc-title {
  display: none;
  font: 700 14px/1.2 system-ui, sans-serif;
  text-align: center;
  padding: 0 12px;
}
.ccm-svc-cta {
  display: block;
  margin-top: 8px;
  padding: 4px 10px;
  font: 600 10px/1 ui-monospace, monospace;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.ccm-svc-cta:hover {
  background: #fff;
  color: #7c6cff;
}
/* Show only the active service title */
.ccm-svc:has(#ccm-svc-0:checked) .ccm-svc-t-0,
.ccm-svc:has(#ccm-svc-1:checked) .ccm-svc-t-1,
.ccm-svc:has(#ccm-svc-2:checked) .ccm-svc-t-2,
.ccm-svc:has(#ccm-svc-3:checked) .ccm-svc-t-3,
.ccm-svc:has(#ccm-svc-4:checked) .ccm-svc-t-4,
.ccm-svc:has(#ccm-svc-5:checked) .ccm-svc-t-5 {
  display: block;
}
/* Pointer rides between disc and satellites; snaps to active position */
.ccm-svc-pointer {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid #7c6cff;
  box-shadow: 0 0 0 4px rgba(124, 108, 255, 0.18);
  transform: rotate(var(--pa)) translateY(calc(var(--r) * -0.55));
  transform-origin: 50% 50%;
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  z-index: 3;
}
.ccm-svc:has(#ccm-svc-0:checked) { --pa: 0deg; }
.ccm-svc:has(#ccm-svc-1:checked) { --pa: 60deg; }
.ccm-svc:has(#ccm-svc-2:checked) { --pa: 120deg; }
.ccm-svc:has(#ccm-svc-3:checked) { --pa: 180deg; }
.ccm-svc:has(#ccm-svc-4:checked) { --pa: 240deg; }
.ccm-svc:has(#ccm-svc-5:checked) { --pa: 300deg; }
/* Satellites positioned around the orbit */
.ccm-svc-sat {
  position: absolute;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #1f1f2e;
  border: 1px solid rgba(124, 108, 255, 0.4);
  color: #c4b5fd;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transform: rotate(calc(var(--i) * var(--ba))) translateY(calc(var(--r) * -1)) rotate(calc(var(--i) * var(--ba) * -1));
  transition: background 0.25s, color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s;
  z-index: 4;
}
.ccm-svc-sat span {
  font-size: 18px;
  line-height: 1;
}
.ccm-svc-sat:hover,
.ccm-svc-sat:focus-visible {
  background: #2a2a3e;
  color: #fff;
  transform: rotate(calc(var(--i) * var(--ba))) translateY(calc(var(--r) * -1)) rotate(calc(var(--i) * var(--ba) * -1)) scale(1.15);
}
.ccm-svc:has(#ccm-svc-0:checked) [for="ccm-svc-0"],
.ccm-svc:has(#ccm-svc-1:checked) [for="ccm-svc-1"],
.ccm-svc:has(#ccm-svc-2:checked) [for="ccm-svc-2"],
.ccm-svc:has(#ccm-svc-3:checked) [for="ccm-svc-3"],
.ccm-svc:has(#ccm-svc-4:checked) [for="ccm-svc-4"],
.ccm-svc:has(#ccm-svc-5:checked) [for="ccm-svc-5"] {
  background: #7c6cff;
  color: #fff;
  border-color: #fff;
  box-shadow: 0 0 0 4px rgba(124, 108, 255, 0.3);
  transform: rotate(calc(var(--i) * var(--ba))) translateY(calc(var(--r) * -1)) rotate(calc(var(--i) * var(--ba) * -1)) scale(1.15);
}
<div class="ccm-svc">
  <input type="radio" name="ccm-svc" id="ccm-svc-0" class="ccm-svc-r" hidden checked>
  <input type="radio" name="ccm-svc" id="ccm-svc-1" class="ccm-svc-r" hidden>
  <input type="radio" name="ccm-svc" id="ccm-svc-2" class="ccm-svc-r" hidden>
  <input type="radio" name="ccm-svc" id="ccm-svc-3" class="ccm-svc-r" hidden>
  <input type="radio" name="ccm-svc" id="ccm-svc-4" class="ccm-svc-r" hidden>
  <input type="radio" name="ccm-svc" id="ccm-svc-5" class="ccm-svc-r" hidden>
  <div class="ccm-svc-orbit" aria-hidden="true"></div>
  <div class="ccm-svc-disc">
    <span class="ccm-svc-title ccm-svc-t-0">Industries</span>
    <span class="ccm-svc-title ccm-svc-t-1">Validation</span>
    <span class="ccm-svc-title ccm-svc-t-2">Engineering</span>
    <span class="ccm-svc-title ccm-svc-t-3">Project Mgmt</span>
    <span class="ccm-svc-title ccm-svc-t-4">Manufacturing</span>
    <span class="ccm-svc-title ccm-svc-t-5">Automation</span>
    <span class="ccm-svc-cta">Learn more →</span>
  </div>
  <div class="ccm-svc-pointer" aria-hidden="true"></div>
  <label for="ccm-svc-0" class="ccm-svc-sat" style="--i:0" aria-label="Industries"><span>♨</span></label>
  <label for="ccm-svc-1" class="ccm-svc-sat" style="--i:1" aria-label="Validation"><span>✓</span></label>
  <label for="ccm-svc-2" class="ccm-svc-sat" style="--i:2" aria-label="Engineering"><span>⚙</span></label>
  <label for="ccm-svc-3" class="ccm-svc-sat" style="--i:3" aria-label="Project Management"><span>◈</span></label>
  <label for="ccm-svc-4" class="ccm-svc-sat" style="--i:4" aria-label="Manufacturing"><span>⛭</span></label>
  <label for="ccm-svc-5" class="ccm-svc-sat" style="--i:5" aria-label="Automation"><span>⌬</span></label>
</div>
03 / 21
Tactile Dial
NEW Pure CSS
A brushed-steel rotary dial flanked by 5 icons arranged on a downward arc. Click any icon and the whole dial rotates left or right (-90°/-45°/0°/+45°/+90°) to "point" at the active selection, with a soft white halo around the chosen icon. Pure CSS via :checked + :has() + a CSS custom property for the rotation angle — adapted from a hardware-knob nav, simplified to drop ~10 PNG dependencies and ~100 lines of jQuery.
.ccm-tac {
  --rot: 0deg;
  position: relative;
  width: 280px;
  height: 240px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  font-family: system-ui, sans-serif;
}
/* Brushed-steel dial — rotates as a whole based on selection */
.ccm-tac-dial {
  position: absolute;
  bottom: 20px;
  left: 50%;
  width: 130px;
  height: 130px;
  margin-left: -65px;
  border-radius: 50%;
  background:
    repeating-conic-gradient(from 0deg, #c9cfd7 0deg, #8b929c 1deg, #c9cfd7 2deg),
    linear-gradient(180deg, #c9cfd7 0%, #2b2f3e 46%, #2b2f3e 54%, #b0b7c1 100%);
  background-blend-mode: overlay, normal;
  box-shadow:
    0 12px 24px rgba(0, 0, 0, 0.4),
    0 4px 8px rgba(0, 0, 0, 0.3),
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    inset 0 -2px 4px rgba(0, 0, 0, 0.4);
  transform: rotate(var(--rot));
  transform-origin: center;
  transition: transform 0.7s cubic-bezier(0.65, 0, 0.35, 1);
  z-index: 2;
}
/* Inner bevel ring */
.ccm-tac-bevel {
  position: absolute;
  inset: 12px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #e8ecf0, #6b7280 70%, #2b2f3e);
  box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.4), inset 0 -2px 4px rgba(0, 0, 0, 0.5);
}
/* Pointer mark on the dial — top, points up toward the icons */
.ccm-tac-mark {
  position: absolute;
  top: 8px;
  left: 50%;
  width: 4px;
  height: 16px;
  margin-left: -2px;
  background: linear-gradient(180deg, #fff, #94a3b8);
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  z-index: 3;
}
/* Drive the dial rotation from the checked radio.
   Angles match the icon positions (each icon is (p-2)*25° from up). */
.ccm-tac:has(#ccm-tac-0:checked) { --rot: -50deg; }
.ccm-tac:has(#ccm-tac-1:checked) { --rot: -25deg; }
.ccm-tac:has(#ccm-tac-2:checked) { --rot: 0deg; }
.ccm-tac:has(#ccm-tac-3:checked) { --rot: 25deg; }
.ccm-tac:has(#ccm-tac-4:checked) { --rot: 50deg; }
/* Dashed orbit guide — full circle centered on the dial's center.
   Icons sit on this orbit (radius 125px from the dial center).
   The dial overlays the bottom half of the ring; only the top arc is visible. */
.ccm-tac-arc {
  position: absolute;
  bottom: -40px;
  left: 50%;
  width: 250px;
  height: 250px;
  margin-left: -125px;
  border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.08);
  pointer-events: none;
  /* Mask the bottom half so the ring only shows where icons live (above the dial) */
  -webkit-mask: linear-gradient(180deg, #000 0%, #000 50%, transparent 50%);
          mask: linear-gradient(180deg, #000 0%, #000 50%, transparent 50%);
}
/* Icons curve as satellites around the top of the dial.
   Each icon is centered on the dial's center, then translated outward
   (radius 125px) and rotated around the dial's center.
   For 5 icons spanning roughly 100° centered at "straight up",
   the angles are -50°, -25°, 0°, +25°, +50°. */
.ccm-tac-i {
  position: absolute;
  bottom: calc(20px + 65px);  /* 20px from container bottom + half the dial height */
  left: 50%;
  width: 38px;
  height: 38px;
  margin: -19px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: #94a3b8;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transform: rotate(calc((var(--p) - 2) * 25deg)) translateY(-125px);
  transform-origin: 50% 50%;
  transition: background 0.25s, color 0.25s, box-shadow 0.3s, border-color 0.25s, transform 0.25s;
  z-index: 4;
}
.ccm-tac-i span {
  font-size: 17px;
  line-height: 1;
  display: inline-block;
  /* Counter-rotate so the icon glyph stays upright */
  transform: rotate(calc((var(--p) - 2) * -25deg));
}
.ccm-tac-i:hover,
.ccm-tac-i:focus-visible {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.25);
  transform: rotate(calc((var(--p) - 2) * 25deg)) translateY(-130px);
}
/* Active icon — soft white halo glow */
.ccm-tac:has(#ccm-tac-0:checked) [for="ccm-tac-0"],
.ccm-tac:has(#ccm-tac-1:checked) [for="ccm-tac-1"],
.ccm-tac:has(#ccm-tac-2:checked) [for="ccm-tac-2"],
.ccm-tac:has(#ccm-tac-3:checked) [for="ccm-tac-3"],
.ccm-tac:has(#ccm-tac-4:checked) [for="ccm-tac-4"] {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.5);
  box-shadow: 0 0 24px 6px rgba(255, 255, 255, 0.18);
  transform: rotate(calc((var(--p) - 2) * 25deg)) translateY(-132px);
}
@media (prefers-reduced-motion: reduce) {
  .ccm-tac-dial { transition: none; }
}
<div class="ccm-tac" role="toolbar" aria-label="Tactile Dial">
  <input type="radio" name="ccm-tac" id="ccm-tac-0" class="ccm-tac-r" hidden>
  <input type="radio" name="ccm-tac" id="ccm-tac-1" class="ccm-tac-r" hidden>
  <input type="radio" name="ccm-tac" id="ccm-tac-2" class="ccm-tac-r" hidden checked>
  <input type="radio" name="ccm-tac" id="ccm-tac-3" class="ccm-tac-r" hidden>
  <input type="radio" name="ccm-tac" id="ccm-tac-4" class="ccm-tac-r" hidden>
  <div class="ccm-tac-dial" aria-hidden="true">
    <div class="ccm-tac-mark"></div>
    <div class="ccm-tac-bevel"></div>
  </div>
  <div class="ccm-tac-arc" aria-hidden="true"></div>
  <label for="ccm-tac-0" class="ccm-tac-i" style="--p:0" aria-label="Email"><span>✉</span></label>
  <label for="ccm-tac-1" class="ccm-tac-i" style="--p:1" aria-label="Photos"><span>◇</span></label>
  <label for="ccm-tac-2" class="ccm-tac-i" style="--p:2" aria-label="Cloud"><span>☁</span></label>
  <label for="ccm-tac-3" class="ccm-tac-i" style="--p:3" aria-label="Portfolio"><span>⊞</span></label>
  <label for="ccm-tac-4" class="ccm-tac-i" style="--p:4" aria-label="Settings"><span>⚙</span></label>
</div>
04 / 21
Fan Reveal
NEW Pure CSS
Six wedges of a 6-color pie that fan open from a closed stack on first paint with a staggered "deal the cards" reveal — each wedge rotates from 0° to its 60° slot with a per-wedge delay. Hover lifts the wedge outward, click locks the selection. Pure CSS keyframe animation, zero JS — adapted from a jQuery-driven menu and rebuilt with proper 60° clip-path geometry.
.ccm-fan {
  position: relative;
  width: 220px;
  height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Lato', system-ui, sans-serif;
  /* Outer ambient halo around the whole wheel */
  filter: drop-shadow(0 0 18px rgba(255, 255, 255, 0.08));
}
/* Each wedge is a 220×220 box clipped via clip-path to a 60° pie slice pointing
   straight UP. Background is a radial gradient that brightens toward the upper
   outer edge — gives each wedge a soft dome look. */
.ccm-fan-w {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 50% 5%, rgba(255, 255, 255, 0.35), transparent 55%),
    radial-gradient(circle at 50% 30%, var(--c) 0%, color-mix(in srgb, var(--c), #000 35%) 100%);
  cursor: pointer;
  clip-path: polygon(
    50% 50%,
    25% 6.7%,
    37.06% 1.92%,
    50% 0%,
    62.94% 1.92%,
    75% 6.7%
  );
  transform-origin: 50% 50%;
  transform: rotate(0deg);
  animation: ccm-fan-deal 0.9s cubic-bezier(0.49, 0.24, 0.32, 0.96) forwards;
  animation-delay: calc(var(--i) * 0.08s);
  transition: filter 0.3s, transform 0.3s;
}
.ccm-fan-w span {
  position: absolute;
  top: 24px;
  left: 50%;
  display: inline-block;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transform: translateX(-50%);
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6), 0 0 8px rgba(255, 255, 255, 0.25);
  white-space: nowrap;
}
@keyframes ccm-fan-deal {
  from { transform: rotate(0deg); }
  to   { transform: rotate(calc(var(--i) * 60deg)); }
}
.ccm-fan-w:hover,
.ccm-fan-w:focus-visible {
  filter:
    brightness(1.18)
    saturate(1.2)
    drop-shadow(0 0 12px color-mix(in srgb, var(--c), white 20%));
  z-index: 2;
}
/* Active wedge — strong color halo, scale-up lift, brighter saturation */
.ccm-fan:has(#ccm-fan-0:checked) [for="ccm-fan-0"],
.ccm-fan:has(#ccm-fan-1:checked) [for="ccm-fan-1"],
.ccm-fan:has(#ccm-fan-2:checked) [for="ccm-fan-2"],
.ccm-fan:has(#ccm-fan-3:checked) [for="ccm-fan-3"],
.ccm-fan:has(#ccm-fan-4:checked) [for="ccm-fan-4"],
.ccm-fan:has(#ccm-fan-5:checked) [for="ccm-fan-5"] {
  filter:
    brightness(1.45)
    saturate(1.5)
    drop-shadow(0 0 22px color-mix(in srgb, var(--c), white 40%))
    drop-shadow(0 0 6px color-mix(in srgb, var(--c), white 60%));
  z-index: 3;
  /* Scale slightly so the active wedge protrudes; rotation is preserved via the
     keyframe's "to" transform — this builds on top of it. */
  transform: rotate(calc(var(--i) * 60deg)) scale(1.06);
}
/* Bigger, brighter label on the active wedge */
.ccm-fan:has(#ccm-fan-0:checked) [for="ccm-fan-0"] span,
.ccm-fan:has(#ccm-fan-1:checked) [for="ccm-fan-1"] span,
.ccm-fan:has(#ccm-fan-2:checked) [for="ccm-fan-2"] span,
.ccm-fan:has(#ccm-fan-3:checked) [for="ccm-fan-3"] span,
.ccm-fan:has(#ccm-fan-4:checked) [for="ccm-fan-4"] span,
.ccm-fan:has(#ccm-fan-5:checked) [for="ccm-fan-5"] span {
  font-size: 13px;
  letter-spacing: 0.1em;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.7),
    0 0 12px rgba(255, 255, 255, 0.6);
}
/* Hub: glassy radial-gradient core with a faint inner highlight + violet rim glow */
.ccm-fan-hub {
  position: absolute;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.18), transparent 55%),
    radial-gradient(circle at center, #1f1f2e 0%, #0a0a14 100%);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    inset 0 -2px 6px rgba(0, 0, 0, 0.5),
    0 0 24px rgba(124, 108, 255, 0.25),
    0 4px 12px rgba(0, 0, 0, 0.5);
  z-index: 4;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .ccm-fan-w {
    animation: none;
    transform: rotate(calc(var(--i) * 60deg));
  }
}
<div class="ccm-fan" role="toolbar" aria-label="Fan Reveal">
  <input type="radio" name="ccm-fan" id="ccm-fan-0" class="ccm-fan-r" hidden>
  <input type="radio" name="ccm-fan" id="ccm-fan-1" class="ccm-fan-r" hidden>
  <input type="radio" name="ccm-fan" id="ccm-fan-2" class="ccm-fan-r" hidden>
  <input type="radio" name="ccm-fan" id="ccm-fan-3" class="ccm-fan-r" hidden>
  <input type="radio" name="ccm-fan" id="ccm-fan-4" class="ccm-fan-r" hidden>
  <input type="radio" name="ccm-fan" id="ccm-fan-5" class="ccm-fan-r" hidden>
  <label for="ccm-fan-0" class="ccm-fan-w" style="--i:0; --c:#255D74" aria-label="Home"><span>home</span></label>
  <label for="ccm-fan-1" class="ccm-fan-w" style="--i:1; --c:#DA353B" aria-label="About"><span>about</span></label>
  <label for="ccm-fan-2" class="ccm-fan-w" style="--i:2; --c:#F89F44" aria-label="Contact"><span>contact</span></label>
  <label for="ccm-fan-3" class="ccm-fan-w" style="--i:3; --c:#78302C" aria-label="Links"><span>links</span></label>
  <label for="ccm-fan-4" class="ccm-fan-w" style="--i:4; --c:#002B55" aria-label="Bio"><span>bio</span></label>
  <label for="ccm-fan-5" class="ccm-fan-w" style="--i:5; --c:#207C6B" aria-label="Store"><span>store</span></label>
  <span class="ccm-fan-hub" aria-hidden="true"></span>
</div>
05 / 21
Petal Fan
Pure CSS
Six action buttons fan out across a 180° upper arc from a central FAB on hover, each with a staggered scale-in. Pure CSS via :hover + transition-delay per child.
.ccm-petal { position: relative; width: 220px; height: 140px; }
.ccm-petal-fab {
  position: absolute; left: 50%; bottom: 0;
  width: 52px; height: 52px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7c6cff, #a78bfa);
  color: #fff; font-size: 26px; line-height: 52px;
  text-align: center; cursor: pointer;
  transform: translateX(-50%);
  transition: transform 0.4s;
  box-shadow: 0 6px 18px rgba(124,108,255,0.4);
  z-index: 2;
}
.ccm-petal-i {
  position: absolute; left: 50%; bottom: 12px;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #c4b5fd; font-size: 14px;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
  border: 1px solid rgba(255,255,255,0.08);
  transform: translate(-50%, 0) rotate(0) translateY(0) rotate(0);
  opacity: 0;
  transition: transform 0.45s cubic-bezier(0.34,1.56,0.64,1), opacity 0.3s;
}
.ccm-petal:hover .ccm-petal-fab,
.ccm-petal-t:checked + .ccm-petal-fab { transform: translateX(-50%) rotate(45deg); }
.ccm-petal:hover .ccm-petal-i,
.ccm-petal-t:checked ~ .ccm-petal-i {
  transform: translate(-50%, 0) rotate(var(--a)) translateY(-72px) rotate(calc(var(--a) * -1));
  opacity: 1;
}
.ccm-petal-i:nth-of-type(1) { transition-delay: 0.05s; }
.ccm-petal-i:nth-of-type(2) { transition-delay: 0.10s; }
.ccm-petal-i:nth-of-type(3) { transition-delay: 0.15s; }
.ccm-petal-i:nth-of-type(4) { transition-delay: 0.20s; }
.ccm-petal-i:nth-of-type(5) { transition-delay: 0.25s; }
.ccm-petal-i:nth-of-type(6) { transition-delay: 0.30s; }
<div class="ccm-petal">
  <input type="checkbox" id="ccm-petal-t" class="ccm-petal-t" hidden>
  <label for="ccm-petal-t" class="ccm-petal-fab" aria-label="Open menu">+</label>
  <a href="#" class="ccm-petal-i" style="--a:-90deg" aria-label="Home">⌂</a>
  <a href="#" class="ccm-petal-i" style="--a:-126deg" aria-label="Search">⌕</a>
  <a href="#" class="ccm-petal-i" style="--a:-162deg" aria-label="Star">★</a>
  <a href="#" class="ccm-petal-i" style="--a:-54deg" aria-label="Like">♥</a>
  <a href="#" class="ccm-petal-i" style="--a:-18deg" aria-label="Mail">✉</a>
  <a href="#" class="ccm-petal-i" style="--a:-198deg" aria-label="Settings">⚙</a>
</div>
06 / 21
Full Circle Wheel
Pure CSS
A B C D E F G H
Eight items evenly distributed around a 360° ring, all visible at rest. Hover any item to scale it up; the central icon stays anchored. Pure trig via CSS variables.
.ccm-wheel {
  position: relative;
  width: 200px; height: 200px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-wheel-c {
  width: 44px; height: 44px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7c6cff, #ff6c8a);
  color: #fff; font-size: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 4px rgba(124,108,255,0.18);
}
.ccm-wheel-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 32px; height: 32px;
  margin: -16px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #c4b5fd;
  font: 700 12px/32px ui-monospace, monospace;
  text-align: center; text-decoration: none;
  border: 1px solid rgba(124,108,255,0.3);
  transform: rotate(var(--a)) translate(78px) rotate(calc(var(--a) * -1));
  transition: transform 0.25s, background 0.2s, color 0.2s;
}
.ccm-wheel-i:hover, .ccm-wheel-i:focus-visible {
  background: #7c6cff; color: #fff;
  transform: rotate(var(--a)) translate(78px) rotate(calc(var(--a) * -1)) scale(1.4);
  z-index: 2;
}
<div class="ccm-wheel">
  <span class="ccm-wheel-c" aria-hidden="true">◉</span>
  <a href="#" class="ccm-wheel-i" style="--a:0deg">A</a>
  <a href="#" class="ccm-wheel-i" style="--a:45deg">B</a>
  <a href="#" class="ccm-wheel-i" style="--a:90deg">C</a>
  <a href="#" class="ccm-wheel-i" style="--a:135deg">D</a>
  <a href="#" class="ccm-wheel-i" style="--a:180deg">E</a>
  <a href="#" class="ccm-wheel-i" style="--a:225deg">F</a>
  <a href="#" class="ccm-wheel-i" style="--a:270deg">G</a>
  <a href="#" class="ccm-wheel-i" style="--a:315deg">H</a>
</div>
07 / 21
Pie Slice Selector
Pure CSS
Four pie slices, each a labelled clickable wedge. Real per-slice hit area via border-radius corners (no overlapping squares) — only the visible pie wedge receives hover and click.
.ccm-pie {
  position: relative;
  width: 170px; height: 170px;
}
.ccm-pie-s {
  position: absolute;
  width: 50%; height: 50%;
  background: var(--c);
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.2s, transform 0.2s;
  display: flex;
}
.ccm-pie-tr { top: 0; right: 0;    border-top-right-radius: 100% 100%;    transform-origin: 0% 100%; }
.ccm-pie-br { bottom: 0; right: 0; border-bottom-right-radius: 100% 100%; transform-origin: 0% 0%; }
.ccm-pie-bl { bottom: 0; left: 0;  border-bottom-left-radius: 100% 100%;  transform-origin: 100% 0%; }
.ccm-pie-tl { top: 0; left: 0;     border-top-left-radius: 100% 100%;     transform-origin: 100% 100%; }
.ccm-pie-s span {
  margin: auto;
  padding: 8px 18px;
  font: 700 12px/1 system-ui, sans-serif;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.ccm-pie-s:hover, .ccm-pie-s:focus-visible {
  filter: brightness(1.18) saturate(1.15);
  transform: scale(1.04);
  z-index: 2;
}
.ccm-pie-h {
  position: absolute;
  top: 50%; left: 50%;
  width: 56px; height: 56px;
  margin: -28px;
  border-radius: 50%;
  background: #15151d;
  border: 3px solid #17171f;
  pointer-events: none;
  z-index: 3;
}
<div class="ccm-pie">
  <a href="#" class="ccm-pie-s ccm-pie-tr" style="--c:#7c6cff" aria-label="Plan"><span>Plan</span></a>
  <a href="#" class="ccm-pie-s ccm-pie-br" style="--c:#ff6c8a" aria-label="Build"><span>Build</span></a>
  <a href="#" class="ccm-pie-s ccm-pie-bl" style="--c:#2ecc8a" aria-label="Test"><span>Test</span></a>
  <a href="#" class="ccm-pie-s ccm-pie-tl" style="--c:#f5a84a" aria-label="Ship"><span>Ship</span></a>
  <span class="ccm-pie-h" aria-hidden="true"></span>
</div>
08 / 21
Donut Sectors
Pure CSS
A 6-sector SVG donut with curved labels following each arc via textPath. On hover the sector pulls outward (split effect), the label brightens, and a subtle scale animation runs. Real hit-tested wedges.
.ccm-donut {
  width: 200px; height: 200px;
  display: block;
  overflow: visible;
  font-family: ui-monospace, 'SF Mono', monospace;
}
.ccm-donut-s {
  cursor: pointer;
  transform-origin: 100px 100px;
  transform: scale(1) translate(0, 0);
  transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1), filter 0.2s;
  text-decoration: none;
}
.ccm-donut-s path {
  stroke: #17171f;
  stroke-width: 2;
  transition: filter 0.2s;
}
.ccm-donut-l {
  font: 700 10px/1 ui-monospace, monospace;
  fill: #fff;
  letter-spacing: 0.18em;
  pointer-events: none;
  opacity: 0.92;
  text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}
.ccm-donut-c {
  font: 700 9px/1 ui-monospace, monospace;
  fill: #c4b5fd;
  letter-spacing: 0.16em;
  pointer-events: none;
}
/* Each sector pulls outward along its center-axis on hover */
.ccm-donut-s:nth-of-type(1):hover, .ccm-donut-s:nth-of-type(1):focus-visible { transform: translate(4px, -4px); }   /* top-right */
.ccm-donut-s:nth-of-type(2):hover, .ccm-donut-s:nth-of-type(2):focus-visible { transform: translate(6px, 0px); }    /* right */
.ccm-donut-s:nth-of-type(3):hover, .ccm-donut-s:nth-of-type(3):focus-visible { transform: translate(4px, 4px); }    /* bottom-right */
.ccm-donut-s:nth-of-type(4):hover, .ccm-donut-s:nth-of-type(4):focus-visible { transform: translate(-4px, 4px); }   /* bottom-left */
.ccm-donut-s:nth-of-type(5):hover, .ccm-donut-s:nth-of-type(5):focus-visible { transform: translate(-6px, 0px); }   /* left */
.ccm-donut-s:nth-of-type(6):hover, .ccm-donut-s:nth-of-type(6):focus-visible { transform: translate(-4px, -4px); }  /* top-left */
.ccm-donut-s:hover path, .ccm-donut-s:focus-visible path { filter: brightness(1.18) saturate(1.1); }
/* Pop-in entry animation on first paint */
.ccm-donut-s {
  animation: ccm-donut-pop 0.5s cubic-bezier(0.34,1.56,0.64,1) backwards;
}
.ccm-donut-s:nth-of-type(1) { animation-delay: 0.05s; }
.ccm-donut-s:nth-of-type(2) { animation-delay: 0.10s; }
.ccm-donut-s:nth-of-type(3) { animation-delay: 0.15s; }
.ccm-donut-s:nth-of-type(4) { animation-delay: 0.20s; }
.ccm-donut-s:nth-of-type(5) { animation-delay: 0.25s; }
.ccm-donut-s:nth-of-type(6) { animation-delay: 0.30s; }
@keyframes ccm-donut-pop {
  from { opacity: 0; transform: scale(0.6); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .ccm-donut-s { animation: none; } }
<svg class="ccm-donut" viewBox="0 0 200 200" role="navigation" aria-label="Donut menu">
  <defs>
    <path id="ccm-donut-arc-1" d="M 100 25 A 75 75 0 0 1 164.95 62.5"/>
    <path id="ccm-donut-arc-2" d="M 164.95 62.5 A 75 75 0 0 1 164.95 137.5"/>
    <path id="ccm-donut-arc-3" d="M 164.95 137.5 A 75 75 0 0 1 100 175"/>
    <path id="ccm-donut-arc-4" d="M 35.05 137.5 A 75 75 0 0 1 35.05 62.5"/>
    <path id="ccm-donut-arc-5" d="M 100 175 A 75 75 0 0 1 35.05 137.5"/>
    <path id="ccm-donut-arc-6" d="M 35.05 62.5 A 75 75 0 0 1 100 25"/>
  </defs>
  <a href="#" class="ccm-donut-s" style="--c:#7c6cff" aria-label="Plan">
    <path d="M 100 100 L 100 10 A 90 90 0 0 1 177.94 55 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-1" startOffset="50%" text-anchor="middle">PLAN</textPath></text>
  </a>
  <a href="#" class="ccm-donut-s" style="--c:#a78bfa" aria-label="Design">
    <path d="M 100 100 L 177.94 55 A 90 90 0 0 1 177.94 145 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-2" startOffset="50%" text-anchor="middle">DESIGN</textPath></text>
  </a>
  <a href="#" class="ccm-donut-s" style="--c:#ff6c8a" aria-label="Build">
    <path d="M 100 100 L 177.94 145 A 90 90 0 0 1 100 190 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-3" startOffset="50%" text-anchor="middle">BUILD</textPath></text>
  </a>
  <a href="#" class="ccm-donut-s" style="--c:#f5a84a" aria-label="Test">
    <path d="M 100 100 L 100 190 A 90 90 0 0 1 22.06 145 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-5" startOffset="50%" text-anchor="middle">TEST</textPath></text>
  </a>
  <a href="#" class="ccm-donut-s" style="--c:#2ecc8a" aria-label="Ship">
    <path d="M 100 100 L 22.06 145 A 90 90 0 0 1 22.06 55 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-4" startOffset="50%" text-anchor="middle">SHIP</textPath></text>
  </a>
  <a href="#" class="ccm-donut-s" style="--c:#00e5ff" aria-label="Learn">
    <path d="M 100 100 L 22.06 55 A 90 90 0 0 1 100 10 Z" fill="var(--c)"/>
    <text class="ccm-donut-l"><textPath href="#ccm-donut-arc-6" startOffset="50%" text-anchor="middle">LEARN</textPath></text>
  </a>
  <circle cx="100" cy="100" r="32" fill="#15151d" stroke="#17171f" stroke-width="3"/>
  <text x="100" y="104" class="ccm-donut-c" text-anchor="middle">MENU</text>
</svg>
09 / 21
Half-Donut Speed Dial
Pure CSS
A B C D E
A bottom-anchored half-arc FAB that fans 5 actions upward in a 180° spread. Each item lands on the arc at an even angle. Pure CSS via :checked.
.ccm-half {
  position: relative;
  width: 220px; height: 130px;
  display: flex; align-items: flex-end; justify-content: center;
}
.ccm-half-fab {
  width: 56px; height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, #2ecc8a, #00e5ff);
  color: #0a0f0c; font-weight: 700; font-size: 24px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: transform 0.4s;
  z-index: 2;
  box-shadow: 0 6px 22px rgba(46,204,138,0.4);
}
input:checked + .ccm-half-fab { transform: rotate(180deg); }
.ccm-half-i {
  position: absolute;
  bottom: 28px; left: 50%;
  width: 36px; height: 36px; margin: -18px;
  border-radius: 50%;
  background: #1f1f2e;
  border: 1.5px solid #2ecc8a;
  color: #2ecc8a; font: 700 12px/36px ui-monospace, monospace;
  text-align: center; text-decoration: none;
  transform: translate(0, 0) rotate(var(--a)) translate(0) rotate(calc(var(--a) * -1));
  opacity: 0;
  transition: transform 0.5s cubic-bezier(0.34,1.56,0.64,1), opacity 0.3s;
}
input:checked ~ .ccm-half-i {
  transform: translate(0, 0) rotate(var(--a)) translate(80px) rotate(calc(var(--a) * -1));
  opacity: 1;
}
.ccm-half-i:nth-of-type(1) { transition-delay: 0.05s; }
.ccm-half-i:nth-of-type(2) { transition-delay: 0.10s; }
.ccm-half-i:nth-of-type(3) { transition-delay: 0.15s; }
.ccm-half-i:nth-of-type(4) { transition-delay: 0.20s; }
.ccm-half-i:nth-of-type(5) { transition-delay: 0.25s; }
<div class="ccm-half">
  <input type="checkbox" id="ccm-half-t" hidden>
  <label for="ccm-half-t" class="ccm-half-fab" aria-label="Toggle">⊕</label>
  <a href="#" class="ccm-half-i" style="--a:180deg">A</a>
  <a href="#" class="ccm-half-i" style="--a:225deg">B</a>
  <a href="#" class="ccm-half-i" style="--a:270deg">C</a>
  <a href="#" class="ccm-half-i" style="--a:315deg">D</a>
  <a href="#" class="ccm-half-i" style="--a:0deg">E</a>
</div>
10 / 21
Glass Dome
Pure CSS
A glass hemisphere with backdrop-blur and an inner highlight; menu items orbit along the dome edge. Items lift on hover. The hero of the dome family.
.ccm-glass-bg {
  padding: 30px;
  border-radius: 14px;
  background: linear-gradient(135deg, #7c6cff 0%, #ff6c8a 100%);
}
.ccm-glass {
  position: relative;
  width: 200px; height: 110px;
}
.ccm-glass-dome {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 110px;
  border-radius: 100px 100px 0 0;
  background: rgba(255,255,255,0.18);
  border: 1px solid rgba(255,255,255,0.45);
  border-bottom: none;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    inset 0 -10px 30px rgba(255,255,255,0.1);
}
.ccm-glass-dome::before {
  content: '';
  position: absolute;
  top: 8px; left: 20%; right: 50%;
  height: 24px;
  border-radius: 50%;
  background: linear-gradient(180deg, rgba(255,255,255,0.55), transparent);
  filter: blur(2px);
}
.ccm-glass-i {
  position: absolute;
  bottom: 0; left: 50%;
  width: 32px; height: 32px; margin-left: -16px;
  border-radius: 50%;
  background: rgba(255,255,255,0.25);
  border: 1px solid rgba(255,255,255,0.5);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
  transform: rotate(var(--a)) translate(95px) rotate(calc(var(--a) * -1));
  transition: transform 0.3s, background 0.2s;
  backdrop-filter: blur(6px);
}
.ccm-glass-i:hover {
  background: rgba(255,255,255,0.45);
  transform: rotate(var(--a)) translate(95px) rotate(calc(var(--a) * -1)) scale(1.18) translateY(-6px);
}
<div class="ccm-glass-bg">
  <div class="ccm-glass">
    <div class="ccm-glass-dome"></div>
    <a href="#" class="ccm-glass-i" style="--a:-150deg">⌂</a>
    <a href="#" class="ccm-glass-i" style="--a:-120deg">⌕</a>
    <a href="#" class="ccm-glass-i" style="--a:-90deg">★</a>
    <a href="#" class="ccm-glass-i" style="--a:-60deg">♥</a>
    <a href="#" class="ccm-glass-i" style="--a:-30deg">⚙</a>
  </div>
</div>
11 / 21
Holographic Dome
Pure CSS
A translucent dome with an iridescent rim that rotates continuously via @property-animated conic gradient. The dome itself stays still; only the rim shimmers.
@property --ccm-holo-a {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.ccm-holo {
  position: relative;
  width: 200px; height: 110px;
}
.ccm-holo-rim {
  position: absolute;
  bottom: -2px; left: -2px; right: -2px;
  height: 114px;
  border-radius: 100px 100px 0 0;
  background: conic-gradient(from var(--ccm-holo-a), #7c6cff, #ff6c8a, #2ecc8a, #00e5ff, #7c6cff);
  animation: ccm-holo-spin 4s linear infinite;
  filter: blur(0.5px);
}
.ccm-holo-dome {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 110px;
  border-radius: 100px 100px 0 0;
  background: rgba(15,15,25,0.7);
  backdrop-filter: blur(8px);
  border-bottom: none;
}
.ccm-holo-i {
  position: absolute;
  bottom: 0; left: 50%;
  width: 32px; height: 32px; margin-left: -16px;
  border-radius: 50%;
  background: #15151d;
  border: 1px solid rgba(255,255,255,0.18);
  color: #c4b5fd;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
  transform: rotate(var(--a)) translate(95px) rotate(calc(var(--a) * -1));
  transition: transform 0.3s, color 0.2s;
}
.ccm-holo-i:hover { color: #fff; transform: rotate(var(--a)) translate(95px) rotate(calc(var(--a) * -1)) scale(1.2); }
@keyframes ccm-holo-spin { to { --ccm-holo-a: 360deg; } }
@media (prefers-reduced-motion: reduce) { .ccm-holo-rim { animation: none; } }
<div class="ccm-holo">
  <div class="ccm-holo-rim"></div>
  <div class="ccm-holo-dome"></div>
  <a href="#" class="ccm-holo-i" style="--a:-150deg">⌂</a>
  <a href="#" class="ccm-holo-i" style="--a:-120deg">⌕</a>
  <a href="#" class="ccm-holo-i" style="--a:-90deg">★</a>
  <a href="#" class="ccm-holo-i" style="--a:-60deg">♥</a>
  <a href="#" class="ccm-holo-i" style="--a:-30deg">⚙</a>
</div>
12 / 21
Single Satellite Orbit
Pure CSS
One satellite continuously orbits the central FAB. Hover the FAB to pause the orbit, click to "lock in" the satellite at its current angle. Pure CSS via :hover paused state.
.ccm-orbit {
  position: relative;
  width: 180px; height: 180px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-orbit-track {
  position: absolute; inset: 20px;
  border-radius: 50%;
  border: 1px dashed rgba(124,108,255,0.3);
}
.ccm-orbit-c {
  width: 48px; height: 48px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7c6cff, #a78bfa);
  color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  font-size: 18px;
  z-index: 2;
  box-shadow: 0 0 0 4px rgba(124,108,255,0.18);
}
.ccm-orbit-sat {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  animation: ccm-orbit-spin 5s linear infinite;
}
.ccm-orbit-i {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #c4b5fd;
  text-decoration: none;
  border: 1px solid rgba(124,108,255,0.4);
  transform: translate(70px) rotate(var(--rev, 0deg));
  animation: ccm-orbit-counter 5s linear infinite;
}
@keyframes ccm-orbit-spin { to { transform: rotate(360deg); } }
@keyframes ccm-orbit-counter { to { transform: translate(70px) rotate(-360deg); } }
.ccm-orbit:hover .ccm-orbit-sat,
.ccm-orbit:hover .ccm-orbit-i { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .ccm-orbit-sat, .ccm-orbit-i { animation: none; }
  .ccm-orbit-i { transform: translate(70px); }
}
<div class="ccm-orbit">
  <div class="ccm-orbit-track"></div>
  <div class="ccm-orbit-sat"><a href="#" class="ccm-orbit-i" aria-label="Item">◆</a></div>
  <a href="#" class="ccm-orbit-c" aria-label="Center">◉</a>
</div>
13 / 21
Solar System
Pure CSS
A central "sun" + 4 "planets" at decreasing orbit radii, each labelled, each orbiting at a different speed (Kepler-style — closer = faster). Real CSS animation, no JS.
.ccm-solar {
  position: relative;
  width: 240px; height: 240px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-solar-c {
  width: 36px; height: 36px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #ffd479, #f5a84a);
  display: inline-flex; align-items: center; justify-content: center;
  color: rgba(255,255,255,0.85); font-size: 20px;
  text-decoration: none;
  box-shadow: 0 0 24px #f5a84a;
  z-index: 5;
}
.ccm-solar-o {
  position: absolute;
  border-radius: 50%;
  border: 1px dashed rgba(255,255,255,0.08);
  animation: ccm-solar-spin linear infinite;
}
.ccm-solar-o1 { inset: 20px;  animation-duration: 11s; }
.ccm-solar-o2 { inset: 50px;  animation-duration: 8s; }
.ccm-solar-o3 { inset: 80px;  animation-duration: 6s; }
.ccm-solar-o4 { inset: 105px; animation-duration: 4s; }
/* Positioner span: rides on the orbit at the 12-o'clock point */
.ccm-solar-pos {
  position: absolute;
  top: 0; left: 50%;
  width: 0; height: 0;
}
/* Planet anchor: counter-rotates so it stays upright; centered on the positioner */
.ccm-solar-p {
  position: absolute;
  top: 0; left: 0;
  width: 24px; height: 24px;
  margin: -12px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #c4b5fd;
  font-size: 13px;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  border: 1px solid rgba(124,108,255,0.4);
  animation: ccm-solar-counter linear infinite;
  animation-duration: inherit;
  z-index: 6;
}
.ccm-solar-o1 .ccm-solar-p { animation-duration: 11s; }
.ccm-solar-o2 .ccm-solar-p { animation-duration: 8s; }
.ccm-solar-o3 .ccm-solar-p { animation-duration: 6s; }
.ccm-solar-o4 .ccm-solar-p { animation-duration: 4s; }
.ccm-solar-p:hover { background: #7c6cff; color: #fff; }
@keyframes ccm-solar-spin { to { transform: rotate(360deg); } }
@keyframes ccm-solar-counter { to { transform: rotate(-360deg); } }
.ccm-solar:hover .ccm-solar-o, .ccm-solar:hover .ccm-solar-p { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .ccm-solar-o, .ccm-solar-p { animation: none; }
}
<div class="ccm-solar">
  <a href="#" class="ccm-solar-c" aria-label="Sun">☀</a>
  <div class="ccm-solar-o ccm-solar-o1"><span class="ccm-solar-pos"><a href="#" class="ccm-solar-p" aria-label="Mercury">☿</a></span></div>
  <div class="ccm-solar-o ccm-solar-o2"><span class="ccm-solar-pos"><a href="#" class="ccm-solar-p" aria-label="Venus">♀</a></span></div>
  <div class="ccm-solar-o ccm-solar-o3"><span class="ccm-solar-pos"><a href="#" class="ccm-solar-p" aria-label="Earth">♁</a></span></div>
  <div class="ccm-solar-o ccm-solar-o4"><span class="ccm-solar-pos"><a href="#" class="ccm-solar-p" aria-label="Mars">♂</a></span></div>
</div>
14 / 21
Hexagonal Honeycomb
Pure CSS
Six hexagonal items arranged around a central hex via real geometry (clip-path polygon). Each cell highlights independently. Honeycomb pattern with mathematical precision.
.ccm-hex {
  position: relative;
  width: 200px; height: 200px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-hex-c, .ccm-hex-i {
  width: 50px; height: 58px;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 11px/1 system-ui, sans-serif;
  text-decoration: none;
  transition: transform 0.2s, background 0.2s;
}
.ccm-hex-c {
  background: linear-gradient(135deg, #7c6cff, #a78bfa);
  color: #fff;
  position: relative; z-index: 2;
}
.ccm-hex-i {
  position: absolute;
  top: 50%; left: 50%; margin: -29px -25px;
  background: #1f1f2e;
  color: #c4b5fd;
  border: 1px solid rgba(124,108,255,0.0);
  transform: rotate(var(--a)) translate(64px) rotate(calc(var(--a) * -1));
}
.ccm-hex-i:hover, .ccm-hex-i:focus-visible {
  background: #2a2a3e; color: #fff;
  transform: rotate(var(--a)) translate(64px) rotate(calc(var(--a) * -1)) scale(1.1);
}
<div class="ccm-hex">
  <a href="#" class="ccm-hex-c" aria-label="Center">◆</a>
  <a href="#" class="ccm-hex-i" style="--a:-90deg" aria-label="N">N</a>
  <a href="#" class="ccm-hex-i" style="--a:-30deg" aria-label="NE">NE</a>
  <a href="#" class="ccm-hex-i" style="--a:30deg" aria-label="SE">SE</a>
  <a href="#" class="ccm-hex-i" style="--a:90deg" aria-label="S">S</a>
  <a href="#" class="ccm-hex-i" style="--a:150deg" aria-label="SW">SW</a>
  <a href="#" class="ccm-hex-i" style="--a:210deg" aria-label="NW">NW</a>
</div>
15 / 21
Rotating Triangle Trio
Pure CSS
A B C
Three triangle items rotate together in a trio formation. Hover any triangle to lift it forward. The whole group rotates slowly on idle for visual interest.
.ccm-tri {
  position: relative;
  width: 180px; height: 180px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-tri-grp {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  animation: ccm-tri-spin 16s linear infinite;
}
@keyframes ccm-tri-spin { to { transform: rotate(360deg); } }
.ccm-tri-i {
  position: absolute;
  width: 48px; height: 48px;
  display: inline-flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #ff6c8a, #f5a84a);
  color: #fff; font: 700 14px/1 system-ui, sans-serif;
  text-decoration: none;
  clip-path: polygon(50% 5%, 95% 95%, 5% 95%);
  transform: rotate(var(--a)) translate(60px);
  transition: transform 0.3s;
}
.ccm-tri-i:hover {
  transform: rotate(var(--a)) translate(60px) scale(1.2);
  filter: drop-shadow(0 4px 12px rgba(255,108,138,0.5));
  z-index: 2;
}
.ccm-tri-c {
  width: 14px; height: 14px;
  border-radius: 50%;
  background: #1f1f2e;
  border: 2px solid #7c6cff;
  position: relative; z-index: 1;
}
@media (prefers-reduced-motion: reduce) { .ccm-tri-grp { animation: none; } }
<div class="ccm-tri">
  <div class="ccm-tri-grp">
    <a href="#" class="ccm-tri-i" style="--a:-90deg">A</a>
    <a href="#" class="ccm-tri-i" style="--a:30deg">B</a>
    <a href="#" class="ccm-tri-i" style="--a:150deg">C</a>
  </div>
  <span class="ccm-tri-c" aria-hidden="true">◯</span>
</div>
16 / 21
Vinyl Record
Pure CSS
A spinning vinyl record menu. The disc rotates continuously; menu items sit on the label area. Pause on hover via animation-play-state. Classic music-app metaphor.
.ccm-vinyl {
  position: relative;
  width: 200px; height: 200px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-vinyl-disc {
  width: 200px; height: 200px;
  border-radius: 50%;
  background:
    radial-gradient(circle at center, #1a1a1a 30%, transparent 31%),
    repeating-radial-gradient(circle at center, #0a0a0a 0, #0a0a0a 1px, #1a1a1a 1px, #1a1a1a 3px);
  position: relative;
  animation: ccm-vinyl-spin 8s linear infinite;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.05), 0 8px 24px rgba(0,0,0,0.5);
}
.ccm-vinyl-disc::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 8px; height: 8px;
  margin: -4px;
  border-radius: 50%;
  background: #f5a84a;
}
.ccm-vinyl-label {
  position: absolute;
  top: 50%; left: 50%;
  width: 100px; height: 100px;
  margin: -50px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff6c8a, #f5a84a);
}
.ccm-vinyl-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 22px; height: 22px;
  margin: -11px;
  border-radius: 50%;
  background: rgba(0,0,0,0.45);
  color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; text-decoration: none;
  transform: rotate(var(--a)) translate(28px) rotate(calc(var(--a) * -1));
}
.ccm-vinyl-i:hover { background: rgba(0,0,0,0.7); }
@keyframes ccm-vinyl-spin { to { transform: rotate(360deg); } }
.ccm-vinyl:hover .ccm-vinyl-disc { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .ccm-vinyl-disc { animation: none; } }
<div class="ccm-vinyl">
  <div class="ccm-vinyl-disc">
    <div class="ccm-vinyl-label">
      <a href="#" class="ccm-vinyl-i" style="--a:-90deg">▶</a>
      <a href="#" class="ccm-vinyl-i" style="--a:0deg">♫</a>
      <a href="#" class="ccm-vinyl-i" style="--a:90deg">♪</a>
      <a href="#" class="ccm-vinyl-i" style="--a:180deg">⏏</a>
    </div>
  </div>
</div>
17 / 21
Compass Rose
Pure CSS
A nautical compass rose with N/E/S/W labels and a needle that points at the active direction. Hover any direction to rotate the needle smoothly via :has().
.ccm-compass {
  position: relative;
  width: 190px; height: 190px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-compass-face {
  width: 160px; height: 160px;
  border-radius: 50%;
  background: radial-gradient(circle at center, #2a2a3e, #15151d);
  border: 2px solid rgba(212,175,55,0.5);
  box-shadow: inset 0 0 24px rgba(212,175,55,0.2), 0 4px 16px rgba(0,0,0,0.4);
}
.ccm-compass-needle {
  position: absolute;
  top: 50%; left: 50%;
  width: 4px; height: 70px;
  margin: -35px -2px;
  background: linear-gradient(180deg, #ff3d6e 0 50%, #d4af37 50% 100%);
  transform-origin: center;
  transform: rotate(0deg);
  transition: transform 0.6s cubic-bezier(0.34,1.56,0.64,1);
  border-radius: 2px;
}
.ccm-compass:has(#ccm-compass-n:checked) .ccm-compass-needle { transform: rotate(0deg); }
.ccm-compass:has(#ccm-compass-e:checked) .ccm-compass-needle { transform: rotate(90deg); }
.ccm-compass:has(#ccm-compass-s:checked) .ccm-compass-needle { transform: rotate(180deg); }
.ccm-compass:has(#ccm-compass-w:checked) .ccm-compass-needle { transform: rotate(270deg); }
.ccm-compass-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 28px; height: 28px;
  margin: -14px;
  border-radius: 50%;
  background: rgba(212,175,55,0.18);
  color: #ffd479;
  font: 700 12px/28px ui-monospace, monospace;
  text-align: center;
  cursor: pointer;
  border: 1px solid rgba(212,175,55,0.4);
  transform: rotate(var(--a)) translate(80px) rotate(calc(var(--a) * -1));
  transition: background 0.2s, color 0.2s, transform 0.2s;
}
.ccm-compass-i:hover { background: rgba(212,175,55,0.4); color: #fff; }
<div class="ccm-compass">
  <input type="radio" name="ccm-compass" id="ccm-compass-n" hidden checked>
  <input type="radio" name="ccm-compass" id="ccm-compass-e" hidden>
  <input type="radio" name="ccm-compass" id="ccm-compass-s" hidden>
  <input type="radio" name="ccm-compass" id="ccm-compass-w" hidden>
  <div class="ccm-compass-face"></div>
  <div class="ccm-compass-needle" aria-hidden="true"></div>
  <label for="ccm-compass-n" class="ccm-compass-i" style="--a:0deg">N</label>
  <label for="ccm-compass-e" class="ccm-compass-i" style="--a:90deg">E</label>
  <label for="ccm-compass-s" class="ccm-compass-i" style="--a:180deg">S</label>
  <label for="ccm-compass-w" class="ccm-compass-i" style="--a:270deg">W</label>
</div>
18 / 21
Iris Aperture
Pure CSS
A camera-lens iris with 6 blades. On hover the blades "stop down" via clip-path + rotation, revealing menu items around the exposed diaphragm.
.ccm-iris {
  position: relative;
  width: 200px; height: 200px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: radial-gradient(circle at center, #0a0a14 30%, #15151d);
  border: 4px solid #2a2a3e;
}
.ccm-iris-blade {
  position: absolute;
  top: 50%; left: 50%;
  width: 100px; height: 100px;
  margin: -50px;
  background: linear-gradient(135deg, #2a2a3e, #15151d);
  clip-path: polygon(50% 0%, 100% 50%, 50% 50%);
  transform: rotate(var(--a)) translateY(-30px);
  transform-origin: center;
  transition: transform 0.5s cubic-bezier(0.65,0,0.35,1);
}
.ccm-iris:hover .ccm-iris-blade {
  transform: rotate(calc(var(--a) + 30deg)) translateY(-50px);
}
.ccm-iris-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 28px; height: 28px;
  margin: -14px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #00e5ff;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  border: 1px solid rgba(0,229,255,0.4);
  transform: rotate(var(--a)) translate(70px) rotate(calc(var(--a) * -1));
  opacity: 0;
  transition: opacity 0.4s ease 0.25s;
  z-index: 2;
}
.ccm-iris:hover .ccm-iris-i { opacity: 1; }
.ccm-iris-i:hover { background: #00e5ff; color: #0a0a14; }
<div class="ccm-iris">
  <div class="ccm-iris-blade" style="--a:0deg"></div>
  <div class="ccm-iris-blade" style="--a:60deg"></div>
  <div class="ccm-iris-blade" style="--a:120deg"></div>
  <div class="ccm-iris-blade" style="--a:180deg"></div>
  <div class="ccm-iris-blade" style="--a:240deg"></div>
  <div class="ccm-iris-blade" style="--a:300deg"></div>
  <a href="#" class="ccm-iris-i" style="--a:-90deg">◐</a>
  <a href="#" class="ccm-iris-i" style="--a:-30deg">◑</a>
  <a href="#" class="ccm-iris-i" style="--a:30deg">◒</a>
  <a href="#" class="ccm-iris-i" style="--a:90deg">◓</a>
  <a href="#" class="ccm-iris-i" style="--a:150deg">◉</a>
  <a href="#" class="ccm-iris-i" style="--a:210deg">○</a>
</div>
19 / 21
Black Hole
Pure CSS
A B C D E F
Items orbit a glowing accretion-disk singularity at all times. On hover the disk pulses brighter and items lift outward slightly — a gravitational "release". Items stay clickable throughout.
.ccm-bh {
  position: relative;
  width: 200px; height: 200px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-bh-disk {
  position: absolute;
  top: 50%; left: 50%;
  width: 110px; height: 110px;
  margin: -55px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, #d4af37, #ff6c8a, #7c6cff, #d4af37);
  filter: blur(6px);
  animation: ccm-bh-spin 8s linear infinite;
  transition: filter 0.4s;
  z-index: 1;
}
.ccm-bh:hover .ccm-bh-disk { filter: blur(4px) brightness(1.3); }
.ccm-bh-core {
  position: absolute;
  top: 50%; left: 50%;
  width: 56px; height: 56px;
  margin: -28px;
  border-radius: 50%;
  background: #000;
  box-shadow: 0 0 30px 8px rgba(0,0,0,0.7);
  z-index: 2;
}
@keyframes ccm-bh-spin { to { transform: rotate(360deg); } }
.ccm-bh-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 30px; height: 30px;
  margin: -15px;
  border-radius: 50%;
  background: #1f1f2e;
  color: #ffd479;
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 12px/1 ui-monospace, monospace;
  text-decoration: none;
  border: 1px solid rgba(212,175,55,0.5);
  transform: rotate(var(--a)) translate(80px) rotate(calc(var(--a) * -1));
  transition: transform 0.5s cubic-bezier(0.34,1.56,0.64,1), background 0.2s, color 0.2s, box-shadow 0.3s;
  z-index: 3;
}
.ccm-bh:hover .ccm-bh-i {
  transform: rotate(var(--a)) translate(92px) rotate(calc(var(--a) * -1));
  box-shadow: 0 0 12px rgba(212,175,55,0.45);
}
.ccm-bh-i:hover, .ccm-bh-i:focus-visible {
  background: #ffd479;
  color: #0a0a14;
  transform: rotate(var(--a)) translate(96px) rotate(calc(var(--a) * -1)) scale(1.2);
  z-index: 4;
}
@media (prefers-reduced-motion: reduce) { .ccm-bh-disk { animation: none; } }
<div class="ccm-bh">
  <div class="ccm-bh-disk" aria-hidden="true"></div>
  <div class="ccm-bh-core" aria-hidden="true"></div>
  <a href="#" class="ccm-bh-i" style="--a:-90deg" aria-label="Item A">A</a>
  <a href="#" class="ccm-bh-i" style="--a:-30deg" aria-label="Item B">B</a>
  <a href="#" class="ccm-bh-i" style="--a:30deg" aria-label="Item C">C</a>
  <a href="#" class="ccm-bh-i" style="--a:90deg" aria-label="Item D">D</a>
  <a href="#" class="ccm-bh-i" style="--a:150deg" aria-label="Item E">E</a>
  <a href="#" class="ccm-bh-i" style="--a:210deg" aria-label="Item F">F</a>
</div>
20 / 21
Chronometer
Pure CSS
An analog watch face with menu items at hour positions. The second-hand sweeps continuously; click any hour position to "set" the menu (hand snaps to that hour).
.ccm-chrono {
  position: relative;
  width: 190px; height: 190px;
  display: flex; align-items: center; justify-content: center;
  --hand: 90deg;
}
.ccm-chrono-face {
  width: 170px; height: 170px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, #f5f5f5, #d8d8d8);
  border: 3px solid #2a2a3e;
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.4);
}
/* Hand: a 60px tall bar rooted at the center, pointing up at rotate(0deg) */
.ccm-chrono-hand {
  position: absolute;
  top: 50%; left: 50%;
  width: 3px; height: 60px;
  margin-left: -1.5px;
  background: #2a2a3e;
  border-radius: 1.5px;
  transform-origin: top center;
  transform: rotate(var(--hand));
  transition: transform 0.6s cubic-bezier(0.34,1.56,0.64,1);
  /* Position the hand's top at the center; the bar extends downward toward 6 o'clock by default */
  /* By rotating 0deg = pointing down (6 o'clock); we want default to point right (3 o'clock) which is rotate(-90deg from down) = -90deg */
}
/* Tip dot at the far end (rotation-origin is at top, so the tip is at the bottom of the bar visually) */
.ccm-chrono-hand::after {
  content: '';
  position: absolute;
  bottom: -3px; left: -2px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #ff3d6e;
}
/* Default (3 o'clock checked) — set on the parent by :has() so the radio order doesn't matter */
.ccm-chrono:has(#ccm-chrono-3:checked)  { --hand: -90deg; }
.ccm-chrono:has(#ccm-chrono-6:checked)  { --hand: 0deg; }
.ccm-chrono:has(#ccm-chrono-9:checked)  { --hand: 90deg; }
.ccm-chrono:has(#ccm-chrono-12:checked) { --hand: 180deg; }
.ccm-chrono-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 28px; height: 28px;
  margin: -14px;
  border-radius: 50%;
  background: rgba(0,0,0,0.05);
  color: #2a2a3e;
  font: 700 13px/28px Georgia, serif;
  text-align: center;
  cursor: pointer;
  transform: rotate(var(--a)) translate(70px) rotate(calc(var(--a) * -1));
  transition: background 0.2s, color 0.2s;
}
.ccm-chrono-i:hover { background: rgba(255,61,110,0.15); color: #ff3d6e; }
<div class="ccm-chrono">
  <input type="radio" name="ccm-chrono" id="ccm-chrono-3" hidden checked>
  <input type="radio" name="ccm-chrono" id="ccm-chrono-6" hidden>
  <input type="radio" name="ccm-chrono" id="ccm-chrono-9" hidden>
  <input type="radio" name="ccm-chrono" id="ccm-chrono-12" hidden>
  <div class="ccm-chrono-face"></div>
  <div class="ccm-chrono-hand" aria-hidden="true"></div>
  <label for="ccm-chrono-12" class="ccm-chrono-i" style="--a:0deg" aria-label="12">12</label>
  <label for="ccm-chrono-3"  class="ccm-chrono-i" style="--a:90deg" aria-label="3">3</label>
  <label for="ccm-chrono-6"  class="ccm-chrono-i" style="--a:180deg" aria-label="6">6</label>
  <label for="ccm-chrono-9"  class="ccm-chrono-i" style="--a:270deg" aria-label="9">9</label>
</div>
21 / 21
Nebula Cloud
Pure CSS
Items float in a soft glowing nebula cloud with no fixed grid positions. Each item has a subtle drift animation; hovering pulls it forward and brightens the surrounding glow.
.ccm-neb {
  position: relative;
  width: 220px; height: 180px;
  display: flex; align-items: center; justify-content: center;
}
.ccm-neb-cloud {
  position: absolute; inset: 0;
  background:
    radial-gradient(60% 50% at 30% 40%, rgba(124,108,255,0.45), transparent 70%),
    radial-gradient(50% 40% at 70% 60%, rgba(255,108,138,0.4), transparent 70%),
    radial-gradient(45% 35% at 50% 30%, rgba(46,184,138,0.3), transparent 70%);
  filter: blur(10px);
  animation: ccm-neb-drift 10s ease-in-out infinite alternate;
}
@keyframes ccm-neb-drift {
  0%   { transform: translate(0,0) scale(1); }
  100% { transform: translate(8px, -6px) scale(1.05); }
}
.ccm-neb-i {
  position: absolute;
  top: 50%; left: 50%;
  width: 36px; height: 36px;
  margin: -18px;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fff; font-size: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none;
  transform: translate(var(--x), var(--y));
  animation: ccm-neb-float 4s ease-in-out var(--d) infinite alternate;
  transition: transform 0.3s, background 0.2s, box-shadow 0.3s;
  z-index: 2;
}
@keyframes ccm-neb-float {
  to { transform: translate(calc(var(--x) + 4px), calc(var(--y) - 4px)); }
}
.ccm-neb-i:hover {
  background: rgba(255,255,255,0.25);
  box-shadow: 0 0 20px rgba(255,255,255,0.4);
  transform: translate(var(--x), var(--y)) scale(1.3);
  animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) { .ccm-neb-cloud, .ccm-neb-i { animation: none; } }
<div class="ccm-neb">
  <div class="ccm-neb-cloud"></div>
  <a href="#" class="ccm-neb-i" style="--x:-50px;--y:-30px;--d:0s">★</a>
  <a href="#" class="ccm-neb-i" style="--x:30px;--y:-40px;--d:1s">✦</a>
  <a href="#" class="ccm-neb-i" style="--x:60px;--y:10px;--d:2s">✧</a>
  <a href="#" class="ccm-neb-i" style="--x:0px;--y:30px;--d:1.5s">◆</a>
  <a href="#" class="ccm-neb-i" style="--x:-60px;--y:20px;--d:0.5s">⬢</a>
</div>
FAQ

Frequently asked questions

What is a CSS circular menu?
A circular menu (also called radial menu or pie menu) is a navigation pattern where items are arranged around a circle, dome, ring, or orbit instead of a flat horizontal or vertical list. Common variants include the floating-action-button speed-dial, full pie/donut wheels, satellite-orbit menus, and dome-shaped containers. They're particularly effective for mobile interfaces and any UI where space is limited.
How do you position items in a circle with pure CSS?
Use trigonometry baked into transforms. Set a CSS custom property --a (the angle) on each child and apply transform: rotate(var(--a)) translate(RADIUS) rotate(calc(var(--a) * -1)). The first rotate orients the item around the center, translate pushes it outward by the radius, and the second counter-rotate keeps the item upright. No JavaScript needed — every demo in this gallery uses this pattern.
How do you add curved text along a donut sector?
Use SVG with a hidden <path> inside <defs> that traces the arc you want the text to follow, then a <text><textPath href="#path-id">YOUR LABEL</textPath></text>. The text-anchor="middle" and startOffset="50%" attributes center the label along the arc. The Donut Sectors demo (#04) uses exactly this pattern with one path per sector.
Are these circular menus accessible?
Yes. Each demo uses real semantic <a> elements with aria-label on icon-only items, visible focus states, and keyboard tab order that follows the visual arrangement. Continuous animations (orbital spins, conic rotations, vinyl spin) honour the prefers-reduced-motion media query — animations stop and items show in their static position for users who request reduced motion.
Do circular menus work on touch devices?
Yes, with a caveat: hover-triggered fan-outs (Petal Fan, Iris Aperture) need a tap-to-toggle pattern on touch, which is handled by the :checked checkbox state in demos that include one (Petal Fan, Half-Donut Speed Dial). The always-visible variants (Full Circle Wheel, Pie Selector, Donut Sectors, Compass, Chronometer, Solar System, Black Hole) work identically on touch and desktop.
Can I use these circular menus in any framework?
Yes. Each demo is plain HTML and CSS (no JavaScript) with no dependencies. Drop the markup into React (with className), Vue, Svelte, Astro or static HTML — the styles work as-is. The CSS-variable-driven angles mean adding more items is just changing the --a values. MIT licensed.

Related collections