26 CSS Circular Menus21 / 26
Retro Vinyl Record Rotating Menu
A turntable you can browse with: the record's grooves are one repeating-radial-gradient, four track chips sit around the platter, and choosing a track drops the tonearm onto the vinyl and starts the spin. Selecting a track is the play action — navigation and player state collapse into one gorgeous object.
Published Updated
The code
<section class="ccm-21" aria-label="Vinyl record menu demo">
<div class="ccm-21__stage">
<div class="ccm-21__deck">
<div class="ccm-21__platter" aria-hidden="true">
<div class="ccm-21__vinyl"><span class="ccm-21__labelface"><b>MIDNIGHT</b><i>SIDE A</i></span></div>
</div>
<div class="ccm-21__arm" aria-hidden="true"><i class="ccm-21__weight"></i><i class="ccm-21__head"></i></div>
<fieldset class="ccm-21__tracks"><legend class="ccm-21__sr">Choose a track</legend>
<div><input type="radio" name="ccm21" id="ccm21-a" value="Neon Rain"><label for="ccm21-a"><b>01</b>Neon Rain<span>3:42</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-b" value="Slow Motion"><label for="ccm21-b"><b>02</b>Slow Motion<span>4:18</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-c" value="Afterglow"><label for="ccm21-c"><b>03</b>Afterglow<span>3:05</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-d" value="Daybreak"><label for="ccm21-d"><b>04</b>Daybreak<span>5:01</span></label></div>
</fieldset>
<p class="ccm-21__now" aria-live="polite">Select a track to drop the needle</p>
</div>
</div>
</section><section class="ccm-21" aria-label="Vinyl record menu demo">
<div class="ccm-21__stage">
<div class="ccm-21__deck">
<div class="ccm-21__platter" aria-hidden="true">
<div class="ccm-21__vinyl"><span class="ccm-21__labelface"><b>MIDNIGHT</b><i>SIDE A</i></span></div>
</div>
<div class="ccm-21__arm" aria-hidden="true"><i class="ccm-21__weight"></i><i class="ccm-21__head"></i></div>
<fieldset class="ccm-21__tracks"><legend class="ccm-21__sr">Choose a track</legend>
<div><input type="radio" name="ccm21" id="ccm21-a" value="Neon Rain"><label for="ccm21-a"><b>01</b>Neon Rain<span>3:42</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-b" value="Slow Motion"><label for="ccm21-b"><b>02</b>Slow Motion<span>4:18</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-c" value="Afterglow"><label for="ccm21-c"><b>03</b>Afterglow<span>3:05</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-d" value="Daybreak"><label for="ccm21-d"><b>04</b>Daybreak<span>5:01</span></label></div>
</fieldset>
<p class="ccm-21__now" aria-live="polite">Select a track to drop the needle</p>
</div>
</div>
</section>.ccm-21,
.ccm-21 *,
.ccm-21 *::before,
.ccm-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ccm-21 {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
--wood: oklch(0.32 0.04 55);
--label: oklch(0.62 0.19 25);
font-family: 'Segoe UI',system-ui,sans-serif;
color: oklch(0.92 0.01 55);
}
.ccm-21__stage {
width: 100%;
height: 100vh;
display: grid;
place-items: center;
border-radius: 20px;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
overflow: hidden;
padding: 28px;
}
.ccm-21__deck {
position: relative;
display: grid;
grid-template-columns: auto 200px;
gap: 26px;
align-items: center;
padding: 26px;
border-radius: 22px;
background: linear-gradient(160deg,var(--wood),oklch(0.26 0.035 50));
box-shadow: 0 30px 70px -30px oklch(0 0 0 / .8),inset 0 1px 0 oklch(1 0 0 / .08);
}
@media (max-width: 560px) {
.ccm-21__deck {
grid-template-columns: 1fr;
}
}
.ccm-21__platter {
position: relative;
width: 280px;
height: 280px;
border-radius: 50%;
background: oklch(0.14 0 0);
box-shadow: 0 12px 30px -10px oklch(0 0 0 / .8),inset 0 0 0 6px oklch(0.2 0 0);
}
.ccm-21__vinyl {
position: absolute;
inset: 10px;
border-radius: 50%;
background: conic-gradient(from 40deg,transparent 0 8%,oklch(1 0 0 / .05) 10%,transparent 13% 48%,oklch(1 0 0 / .05) 51%,transparent 54%),radial-gradient(circle,var(--label) 0 24%,transparent 24.5%),repeating-radial-gradient(circle,oklch(0.23 0 0) 0 2px,oklch(0.17 0 0) 2px 4px);
display: grid;
place-items: center;
animation: ccm21-spin 1.8s linear infinite;
animation-play-state: paused;
}
.ccm-21__deck.is-playing .ccm-21__vinyl {
animation-play-state: running;
}
.ccm-21__labelface {
display: grid;
place-items: center;
gap: 1px;
width: 120px;
height: 120px;
border-radius: 50%;
text-align: center;
}
.ccm-21__labelface b {
font-size: 14px;
letter-spacing: .18em;
}
.ccm-21__labelface i {
font-style: normal;
font-size: 9.5px;
letter-spacing: .3em;
opacity: .8;
}
.ccm-21__labelface::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: oklch(0.9 0.01 55);
box-shadow: inset 0 1px 2px oklch(0 0 0 / .6);
}
.ccm-21__arm {
position: absolute;
left: 266px;
top: 30px;
width: 16px;
height: 190px;
rotate: -24deg;
transform-origin: 8px 22px;
transition: rotate .8s cubic-bezier(.4,0,.3,1);
z-index: 2;
filter: drop-shadow(0 8px 14px oklch(0 0 0 / .6));
}
.ccm-21__deck.is-playing .ccm-21__arm {
rotate: 0deg;
}
.ccm-21__arm::before {
content: "";
position: absolute;
left: 6px;
top: 20px;
width: 4px;
height: 150px;
border-radius: 2px;
background: linear-gradient(90deg,oklch(0.78 0.01 80),oklch(0.6 0.01 80));
}
.ccm-21__weight {
position: absolute;
left: 0;
top: 0;
width: 16px;
height: 26px;
border-radius: 5px;
background: oklch(0.5 0.02 80);
}
.ccm-21__head {
position: absolute;
left: 2px;
bottom: 6px;
width: 12px;
height: 22px;
border-radius: 3px;
background: oklch(0.72 0.12 80);
}
.ccm-21__tracks {
border: none;
display: grid;
gap: 9px;
}
.ccm-21__sr {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
.ccm-21__tracks input {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
}
.ccm-21__tracks label {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border-radius: 12px;
background: oklch(1 0 0 / .06);
border: 1px solid oklch(1 0 0 / .12);
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
transition: background .2s,border-color .2s;
}
.ccm-21__tracks label:hover {
background: oklch(1 0 0 / .12);
}
.ccm-21__tracks :checked + label {
background: color-mix(in oklab,var(--label) 30%,transparent);
border-color: var(--label);
}
.ccm-21__tracks :focus-visible + label {
outline: 3px solid var(--label);
outline-offset: 2px;
}
.ccm-21__tracks b {
font-size: 11px;
color: oklch(0.75 0.1 30);
font-variant-numeric: tabular-nums;
}
.ccm-21__tracks span {
margin-left: auto;
font-size: 11.5px;
opacity: .6;
font-variant-numeric: tabular-nums;
}
.ccm-21__now {
position: absolute;
bottom: 8px;
left: 26px;
font-size: 11.5px;
letter-spacing: .06em;
color: oklch(0.7 0.04 55 / .8);
}
@keyframes ccm21-spin {
to {
rotate: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.ccm-21__vinyl {
animation: none;
}
.ccm-21 * {
transition-duration: .01s !important;
}
}.ccm-21,
.ccm-21 *,
.ccm-21 *::before,
.ccm-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ccm-21 {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
--wood: oklch(0.32 0.04 55);
--label: oklch(0.62 0.19 25);
font-family: 'Segoe UI',system-ui,sans-serif;
color: oklch(0.92 0.01 55);
}
.ccm-21__stage {
width: 100%;
height: 100vh;
display: grid;
place-items: center;
border-radius: 20px;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
overflow: hidden;
padding: 28px;
}
.ccm-21__deck {
position: relative;
display: grid;
grid-template-columns: auto 200px;
gap: 26px;
align-items: center;
padding: 26px;
border-radius: 22px;
background: linear-gradient(160deg,var(--wood),oklch(0.26 0.035 50));
box-shadow: 0 30px 70px -30px oklch(0 0 0 / .8),inset 0 1px 0 oklch(1 0 0 / .08);
}
@media (max-width: 560px) {
.ccm-21__deck {
grid-template-columns: 1fr;
}
}
.ccm-21__platter {
position: relative;
width: 280px;
height: 280px;
border-radius: 50%;
background: oklch(0.14 0 0);
box-shadow: 0 12px 30px -10px oklch(0 0 0 / .8),inset 0 0 0 6px oklch(0.2 0 0);
}
.ccm-21__vinyl {
position: absolute;
inset: 10px;
border-radius: 50%;
background: conic-gradient(from 40deg,transparent 0 8%,oklch(1 0 0 / .05) 10%,transparent 13% 48%,oklch(1 0 0 / .05) 51%,transparent 54%),radial-gradient(circle,var(--label) 0 24%,transparent 24.5%),repeating-radial-gradient(circle,oklch(0.23 0 0) 0 2px,oklch(0.17 0 0) 2px 4px);
display: grid;
place-items: center;
animation: ccm21-spin 1.8s linear infinite;
animation-play-state: paused;
}
.ccm-21__deck.is-playing .ccm-21__vinyl {
animation-play-state: running;
}
.ccm-21__labelface {
display: grid;
place-items: center;
gap: 1px;
width: 120px;
height: 120px;
border-radius: 50%;
text-align: center;
}
.ccm-21__labelface b {
font-size: 14px;
letter-spacing: .18em;
}
.ccm-21__labelface i {
font-style: normal;
font-size: 9.5px;
letter-spacing: .3em;
opacity: .8;
}
.ccm-21__labelface::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: oklch(0.9 0.01 55);
box-shadow: inset 0 1px 2px oklch(0 0 0 / .6);
}
.ccm-21__arm {
position: absolute;
left: 266px;
top: 30px;
width: 16px;
height: 190px;
rotate: -24deg;
transform-origin: 8px 22px;
transition: rotate .8s cubic-bezier(.4,0,.3,1);
z-index: 2;
filter: drop-shadow(0 8px 14px oklch(0 0 0 / .6));
}
.ccm-21__deck.is-playing .ccm-21__arm {
rotate: 0deg;
}
.ccm-21__arm::before {
content: "";
position: absolute;
left: 6px;
top: 20px;
width: 4px;
height: 150px;
border-radius: 2px;
background: linear-gradient(90deg,oklch(0.78 0.01 80),oklch(0.6 0.01 80));
}
.ccm-21__weight {
position: absolute;
left: 0;
top: 0;
width: 16px;
height: 26px;
border-radius: 5px;
background: oklch(0.5 0.02 80);
}
.ccm-21__head {
position: absolute;
left: 2px;
bottom: 6px;
width: 12px;
height: 22px;
border-radius: 3px;
background: oklch(0.72 0.12 80);
}
.ccm-21__tracks {
border: none;
display: grid;
gap: 9px;
}
.ccm-21__sr {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
.ccm-21__tracks input {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
}
.ccm-21__tracks label {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border-radius: 12px;
background: oklch(1 0 0 / .06);
border: 1px solid oklch(1 0 0 / .12);
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
transition: background .2s,border-color .2s;
}
.ccm-21__tracks label:hover {
background: oklch(1 0 0 / .12);
}
.ccm-21__tracks :checked + label {
background: color-mix(in oklab,var(--label) 30%,transparent);
border-color: var(--label);
}
.ccm-21__tracks :focus-visible + label {
outline: 3px solid var(--label);
outline-offset: 2px;
}
.ccm-21__tracks b {
font-size: 11px;
color: oklch(0.75 0.1 30);
font-variant-numeric: tabular-nums;
}
.ccm-21__tracks span {
margin-left: auto;
font-size: 11.5px;
opacity: .6;
font-variant-numeric: tabular-nums;
}
.ccm-21__now {
position: absolute;
bottom: 8px;
left: 26px;
font-size: 11.5px;
letter-spacing: .06em;
color: oklch(0.7 0.04 55 / .8);
}
@keyframes ccm21-spin {
to {
rotate: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.ccm-21__vinyl {
animation: none;
}
.ccm-21 * {
transition-duration: .01s !important;
}
}(() => {
const deck = document.querySelector('.ccm-21__deck');
const now = deck.querySelector('.ccm-21__now');
deck.addEventListener('change', (e) => {
if (e.target.name !== 'ccm21') return;
deck.classList.add('is-playing');
now.textContent = '▶ Now playing — ' + e.target.value;
});
})();(() => {
const deck = document.querySelector('.ccm-21__deck');
const now = deck.querySelector('.ccm-21__now');
deck.addEventListener('change', (e) => {
if (e.target.name !== 'ccm21') return;
deck.classList.add('is-playing');
now.textContent = '▶ Now playing — ' + e.target.value;
});
})();Here's a working CSS Circular Menu from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Retro Vinyl Record Rotating Menu
Source: https://codefronts.com/navigation/css-circular-menus/vinyl-record/
A turntable you can browse with: the record's grooves are one repeating-radial-gradient, four track chips sit around the platter, and choosing a track drops the tonearm onto the vinyl and starts the spin. Selecting a track is the play action — navigation and player state collapse into one gorgeous object.
## HTML
```html
<section class="ccm-21" aria-label="Vinyl record menu demo">
<div class="ccm-21__stage">
<div class="ccm-21__deck">
<div class="ccm-21__platter" aria-hidden="true">
<div class="ccm-21__vinyl"><span class="ccm-21__labelface"><b>MIDNIGHT</b><i>SIDE A</i></span></div>
</div>
<div class="ccm-21__arm" aria-hidden="true"><i class="ccm-21__weight"></i><i class="ccm-21__head"></i></div>
<fieldset class="ccm-21__tracks"><legend class="ccm-21__sr">Choose a track</legend>
<div><input type="radio" name="ccm21" id="ccm21-a" value="Neon Rain"><label for="ccm21-a"><b>01</b>Neon Rain<span>3:42</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-b" value="Slow Motion"><label for="ccm21-b"><b>02</b>Slow Motion<span>4:18</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-c" value="Afterglow"><label for="ccm21-c"><b>03</b>Afterglow<span>3:05</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-d" value="Daybreak"><label for="ccm21-d"><b>04</b>Daybreak<span>5:01</span></label></div>
</fieldset>
<p class="ccm-21__now" aria-live="polite">Select a track to drop the needle</p>
</div>
</div>
</section>
```
## CSS
```css
.ccm-21,
.ccm-21 *,
.ccm-21 *::before,
.ccm-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ccm-21 {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
--wood: oklch(0.32 0.04 55);
--label: oklch(0.62 0.19 25);
font-family: 'Segoe UI',system-ui,sans-serif;
color: oklch(0.92 0.01 55);
}
.ccm-21__stage {
width: 100%;
height: 100vh;
display: grid;
place-items: center;
border-radius: 20px;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
overflow: hidden;
padding: 28px;
}
.ccm-21__deck {
position: relative;
display: grid;
grid-template-columns: auto 200px;
gap: 26px;
align-items: center;
padding: 26px;
border-radius: 22px;
background: linear-gradient(160deg,var(--wood),oklch(0.26 0.035 50));
box-shadow: 0 30px 70px -30px oklch(0 0 0 / .8),inset 0 1px 0 oklch(1 0 0 / .08);
}
@media (max-width: 560px) {
.ccm-21__deck {
grid-template-columns: 1fr;
}
}
.ccm-21__platter {
position: relative;
width: 280px;
height: 280px;
border-radius: 50%;
background: oklch(0.14 0 0);
box-shadow: 0 12px 30px -10px oklch(0 0 0 / .8),inset 0 0 0 6px oklch(0.2 0 0);
}
.ccm-21__vinyl {
position: absolute;
inset: 10px;
border-radius: 50%;
background: conic-gradient(from 40deg,transparent 0 8%,oklch(1 0 0 / .05) 10%,transparent 13% 48%,oklch(1 0 0 / .05) 51%,transparent 54%),radial-gradient(circle,var(--label) 0 24%,transparent 24.5%),repeating-radial-gradient(circle,oklch(0.23 0 0) 0 2px,oklch(0.17 0 0) 2px 4px);
display: grid;
place-items: center;
animation: ccm21-spin 1.8s linear infinite;
animation-play-state: paused;
}
.ccm-21__deck.is-playing .ccm-21__vinyl {
animation-play-state: running;
}
.ccm-21__labelface {
display: grid;
place-items: center;
gap: 1px;
width: 120px;
height: 120px;
border-radius: 50%;
text-align: center;
}
.ccm-21__labelface b {
font-size: 14px;
letter-spacing: .18em;
}
.ccm-21__labelface i {
font-style: normal;
font-size: 9.5px;
letter-spacing: .3em;
opacity: .8;
}
.ccm-21__labelface::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: oklch(0.9 0.01 55);
box-shadow: inset 0 1px 2px oklch(0 0 0 / .6);
}
.ccm-21__arm {
position: absolute;
left: 266px;
top: 30px;
width: 16px;
height: 190px;
rotate: -24deg;
transform-origin: 8px 22px;
transition: rotate .8s cubic-bezier(.4,0,.3,1);
z-index: 2;
filter: drop-shadow(0 8px 14px oklch(0 0 0 / .6));
}
.ccm-21__deck.is-playing .ccm-21__arm {
rotate: 0deg;
}
.ccm-21__arm::before {
content: "";
position: absolute;
left: 6px;
top: 20px;
width: 4px;
height: 150px;
border-radius: 2px;
background: linear-gradient(90deg,oklch(0.78 0.01 80),oklch(0.6 0.01 80));
}
.ccm-21__weight {
position: absolute;
left: 0;
top: 0;
width: 16px;
height: 26px;
border-radius: 5px;
background: oklch(0.5 0.02 80);
}
.ccm-21__head {
position: absolute;
left: 2px;
bottom: 6px;
width: 12px;
height: 22px;
border-radius: 3px;
background: oklch(0.72 0.12 80);
}
.ccm-21__tracks {
border: none;
display: grid;
gap: 9px;
}
.ccm-21__sr {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
.ccm-21__tracks input {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
}
.ccm-21__tracks label {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border-radius: 12px;
background: oklch(1 0 0 / .06);
border: 1px solid oklch(1 0 0 / .12);
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
transition: background .2s,border-color .2s;
}
.ccm-21__tracks label:hover {
background: oklch(1 0 0 / .12);
}
.ccm-21__tracks :checked + label {
background: color-mix(in oklab,var(--label) 30%,transparent);
border-color: var(--label);
}
.ccm-21__tracks :focus-visible + label {
outline: 3px solid var(--label);
outline-offset: 2px;
}
.ccm-21__tracks b {
font-size: 11px;
color: oklch(0.75 0.1 30);
font-variant-numeric: tabular-nums;
}
.ccm-21__tracks span {
margin-left: auto;
font-size: 11.5px;
opacity: .6;
font-variant-numeric: tabular-nums;
}
.ccm-21__now {
position: absolute;
bottom: 8px;
left: 26px;
font-size: 11.5px;
letter-spacing: .06em;
color: oklch(0.7 0.04 55 / .8);
}
@keyframes ccm21-spin {
to {
rotate: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.ccm-21__vinyl {
animation: none;
}
.ccm-21 * {
transition-duration: .01s !important;
}
}
```
## JavaScript
```js
(() => {
const deck = document.querySelector('.ccm-21__deck');
const now = deck.querySelector('.ccm-21__now');
deck.addEventListener('change', (e) => {
if (e.target.name !== 'ccm21') return;
deck.classList.add('is-playing');
now.textContent = '▶ Now playing — ' + e.target.value;
});
})();
```Here's a working CSS Circular Menu from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Retro Vinyl Record Rotating Menu
Source: https://codefronts.com/navigation/css-circular-menus/vinyl-record/
A turntable you can browse with: the record's grooves are one repeating-radial-gradient, four track chips sit around the platter, and choosing a track drops the tonearm onto the vinyl and starts the spin. Selecting a track is the play action — navigation and player state collapse into one gorgeous object.
## HTML
```html
<section class="ccm-21" aria-label="Vinyl record menu demo">
<div class="ccm-21__stage">
<div class="ccm-21__deck">
<div class="ccm-21__platter" aria-hidden="true">
<div class="ccm-21__vinyl"><span class="ccm-21__labelface"><b>MIDNIGHT</b><i>SIDE A</i></span></div>
</div>
<div class="ccm-21__arm" aria-hidden="true"><i class="ccm-21__weight"></i><i class="ccm-21__head"></i></div>
<fieldset class="ccm-21__tracks"><legend class="ccm-21__sr">Choose a track</legend>
<div><input type="radio" name="ccm21" id="ccm21-a" value="Neon Rain"><label for="ccm21-a"><b>01</b>Neon Rain<span>3:42</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-b" value="Slow Motion"><label for="ccm21-b"><b>02</b>Slow Motion<span>4:18</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-c" value="Afterglow"><label for="ccm21-c"><b>03</b>Afterglow<span>3:05</span></label></div>
<div><input type="radio" name="ccm21" id="ccm21-d" value="Daybreak"><label for="ccm21-d"><b>04</b>Daybreak<span>5:01</span></label></div>
</fieldset>
<p class="ccm-21__now" aria-live="polite">Select a track to drop the needle</p>
</div>
</div>
</section>
```
## CSS
```css
.ccm-21,
.ccm-21 *,
.ccm-21 *::before,
.ccm-21 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.ccm-21 {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
--wood: oklch(0.32 0.04 55);
--label: oklch(0.62 0.19 25);
font-family: 'Segoe UI',system-ui,sans-serif;
color: oklch(0.92 0.01 55);
}
.ccm-21__stage {
width: 100%;
height: 100vh;
display: grid;
place-items: center;
border-radius: 20px;
background: linear-gradient(180deg,oklch(0.27 0.03 50),oklch(0.2 0.025 45));
overflow: hidden;
padding: 28px;
}
.ccm-21__deck {
position: relative;
display: grid;
grid-template-columns: auto 200px;
gap: 26px;
align-items: center;
padding: 26px;
border-radius: 22px;
background: linear-gradient(160deg,var(--wood),oklch(0.26 0.035 50));
box-shadow: 0 30px 70px -30px oklch(0 0 0 / .8),inset 0 1px 0 oklch(1 0 0 / .08);
}
@media (max-width: 560px) {
.ccm-21__deck {
grid-template-columns: 1fr;
}
}
.ccm-21__platter {
position: relative;
width: 280px;
height: 280px;
border-radius: 50%;
background: oklch(0.14 0 0);
box-shadow: 0 12px 30px -10px oklch(0 0 0 / .8),inset 0 0 0 6px oklch(0.2 0 0);
}
.ccm-21__vinyl {
position: absolute;
inset: 10px;
border-radius: 50%;
background: conic-gradient(from 40deg,transparent 0 8%,oklch(1 0 0 / .05) 10%,transparent 13% 48%,oklch(1 0 0 / .05) 51%,transparent 54%),radial-gradient(circle,var(--label) 0 24%,transparent 24.5%),repeating-radial-gradient(circle,oklch(0.23 0 0) 0 2px,oklch(0.17 0 0) 2px 4px);
display: grid;
place-items: center;
animation: ccm21-spin 1.8s linear infinite;
animation-play-state: paused;
}
.ccm-21__deck.is-playing .ccm-21__vinyl {
animation-play-state: running;
}
.ccm-21__labelface {
display: grid;
place-items: center;
gap: 1px;
width: 120px;
height: 120px;
border-radius: 50%;
text-align: center;
}
.ccm-21__labelface b {
font-size: 14px;
letter-spacing: .18em;
}
.ccm-21__labelface i {
font-style: normal;
font-size: 9.5px;
letter-spacing: .3em;
opacity: .8;
}
.ccm-21__labelface::after {
content: "";
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: oklch(0.9 0.01 55);
box-shadow: inset 0 1px 2px oklch(0 0 0 / .6);
}
.ccm-21__arm {
position: absolute;
left: 266px;
top: 30px;
width: 16px;
height: 190px;
rotate: -24deg;
transform-origin: 8px 22px;
transition: rotate .8s cubic-bezier(.4,0,.3,1);
z-index: 2;
filter: drop-shadow(0 8px 14px oklch(0 0 0 / .6));
}
.ccm-21__deck.is-playing .ccm-21__arm {
rotate: 0deg;
}
.ccm-21__arm::before {
content: "";
position: absolute;
left: 6px;
top: 20px;
width: 4px;
height: 150px;
border-radius: 2px;
background: linear-gradient(90deg,oklch(0.78 0.01 80),oklch(0.6 0.01 80));
}
.ccm-21__weight {
position: absolute;
left: 0;
top: 0;
width: 16px;
height: 26px;
border-radius: 5px;
background: oklch(0.5 0.02 80);
}
.ccm-21__head {
position: absolute;
left: 2px;
bottom: 6px;
width: 12px;
height: 22px;
border-radius: 3px;
background: oklch(0.72 0.12 80);
}
.ccm-21__tracks {
border: none;
display: grid;
gap: 9px;
}
.ccm-21__sr {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
.ccm-21__tracks input {
position: absolute;
width: 1px;
height: 1px;
clip-path: inset(50%);
overflow: hidden;
}
.ccm-21__tracks label {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border-radius: 12px;
background: oklch(1 0 0 / .06);
border: 1px solid oklch(1 0 0 / .12);
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
transition: background .2s,border-color .2s;
}
.ccm-21__tracks label:hover {
background: oklch(1 0 0 / .12);
}
.ccm-21__tracks :checked + label {
background: color-mix(in oklab,var(--label) 30%,transparent);
border-color: var(--label);
}
.ccm-21__tracks :focus-visible + label {
outline: 3px solid var(--label);
outline-offset: 2px;
}
.ccm-21__tracks b {
font-size: 11px;
color: oklch(0.75 0.1 30);
font-variant-numeric: tabular-nums;
}
.ccm-21__tracks span {
margin-left: auto;
font-size: 11.5px;
opacity: .6;
font-variant-numeric: tabular-nums;
}
.ccm-21__now {
position: absolute;
bottom: 8px;
left: 26px;
font-size: 11.5px;
letter-spacing: .06em;
color: oklch(0.7 0.04 55 / .8);
}
@keyframes ccm21-spin {
to {
rotate: 360deg;
}
}
@media (prefers-reduced-motion: reduce) {
.ccm-21__vinyl {
animation: none;
}
.ccm-21 * {
transition-duration: .01s !important;
}
}
```
## JavaScript
```js
(() => {
const deck = document.querySelector('.ccm-21__deck');
const now = deck.querySelector('.ccm-21__now');
deck.addEventListener('change', (e) => {
if (e.target.name !== 'ccm21') return;
deck.classList.add('is-playing');
now.textContent = '▶ Now playing — ' + e.target.value;
});
})();
```How this works
The vinyl is entirely gradients — grooves, sheen and label:
background:
radial-gradient(circle, var(--label) 0 24%, transparent 24%),
repeating-radial-gradient(circle,
oklch(0.22 0 0) 0 2px, oklch(0.16 0 0) 2px 4px);plus a conic highlight layered on top for the light sweep across the grooves. The spin is a paused infinite rotation: animation-play-state flips between paused and running, so the record resumes from its exact groove — stopping a rotation by removing the animation snaps it to 0° and shatters the illusion.
The tonearm is a single element pivoting on its counterweight via transform-origin; play state just toggles its rotation between rest (−24°) and groove (0°). Track chips are radios; the record itself is decorative and aria-hidden.
Make it yours
- Label art: the center is a normal element — drop album art in with
background-image. - RPM: the spin duration (1.8s ≈ 33⅓ rpm at this scale reads right).
- Groove density: the 2px/4px stops in the repeating gradient.
- Wire the
changeevent to a real<audio>element's play().
Gotchas — read before shipping
- Pause with
animation-play-state, never by removing the animation class — removal resets rotation to 0. - The tonearm needs its pivot at the physical bearing (
transform-originnear one end), not its center. - Keep the spinning surface free of text; readable labels belong on static chips outside the platter.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 113+ | 111+ |
Floor set by oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome any.
repeating-radial-gradient + conic-gradient + transforms. Runs basically everywhere; oklch is the only modern nicety.