21 CSS Circular & Radial Menu Designs
Tactile Dial
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.
Tactile Dial the 3rd of 21 designs in the 21 CSS Circular & Radial Menu Designs collection. The design is implemented in pure CSS — no JavaScript required. Copy the HTML and CSS panels below into your project. Because the demo is pure CSS, it works in any framework or templating engine you happen to use. The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<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> .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;
}
.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;
}
.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);
}
.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;
}
.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;
}
.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;
-webkit-mask: linear-gradient(180deg, #000 0%, #000 50%, transparent 50%);
mask: linear-gradient(180deg, #000 0%, #000 50%, transparent 50%);
}
.ccm-tac-i {
position: absolute;
bottom: calc(20px + 65px);
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;
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);
}
.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);
}