18 CSS Play / Pause Button Designs
Vinyl Record
A miniature vinyl record. While playing it spins continuously; on pause the rotation slows to a stop with a subtle ease-out — the canonical music-app metaphor done as a single CSS button.
Vinyl Record the 2nd of 18 designs in the 18 CSS Play / Pause Button Designs collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<button class="pp-vinyl" type="button" aria-pressed="false" aria-label="Play" data-pp>
<span class="pp-vinyl-disc" aria-hidden="true">
<span class="pp-vinyl-shine"></span>
<span class="pp-vinyl-label"></span>
<span class="pp-vinyl-icon"></span>
</span>
</button> .pp-vinyl {
width: 72px;
height: 72px;
background: transparent;
border: 0;
padding: 0;
cursor: pointer;
border-radius: 50%;
}
.pp-vinyl:focus-visible {
outline: 3px solid rgba(251, 191, 36, 0.5);
outline-offset: 3px;
}
.pp-vinyl-disc {
display: block;
position: relative;
width: 100%;
height: 100%;
border-radius: 50%;
background: repeating-radial-gradient(circle at 50% 50%, #0a0a0a 0 1.5px, #1c1c1c 1.5px 3px);
box-shadow:
0 8px 22px -4px rgba(0, 0, 0, 0.7),
inset 0 0 0 2px rgba(255, 255, 255, 0.08);
/* Asymmetric reflection that makes the rotation visible */
}
.pp-vinyl-disc::before {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: linear-gradient(
135deg,
transparent 30%,
rgba(255, 255, 255, 0.18) 50%,
transparent 70%
);
pointer-events: none;
}
/* The shine span — a brighter, narrow streak that catches the eye */
.pp-vinyl-shine {
position: absolute;
inset: 0;
border-radius: 50%;
background: conic-gradient(
from 0deg,
transparent 0deg,
rgba(255, 255, 255, 0.22) 18deg,
transparent 36deg,
transparent 360deg
);
pointer-events: none;
}
.pp-vinyl[aria-pressed="true"] .pp-vinyl-disc {
animation: ppVinylSpin 2.4s linear infinite;
}
@keyframes ppVinylSpin {
to {
transform: rotate(360deg);
}
}
.pp-vinyl-label {
position: absolute;
inset: 0;
margin: auto;
width: 40%;
height: 40%;
border-radius: 50%;
background:
radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.4), transparent 40%),
linear-gradient(135deg, #fbbf24, #b45309);
box-shadow:
inset 0 0 0 1px rgba(0, 0, 0, 0.35),
inset 0 -2px 4px rgba(0, 0, 0, 0.25);
}
.pp-vinyl-icon {
position: absolute;
inset: 0;
margin: auto;
width: 8%;
height: 8%;
border-radius: 50%;
background: #15151d;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);
}
@media (prefers-reduced-motion: reduce) {
.pp-vinyl,
.pp-vinyl * {
animation: none !important;
}
}
// ── Drop this on every page where you render a play/pause button ──
// Toggles aria-pressed + aria-label on click. The CSS handles all visuals.
document.querySelectorAll('[data-pp]').forEach(function (btn) {
btn.addEventListener('click', function () {
var playing = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!playing));
btn.setAttribute('aria-label', !playing ? 'Pause' : 'Play');
});
}); More from 18 CSS Play / Pause Button Designs
Minimal OutlineGradient DiscVoice MemoFloating FABProgress RingMagnetic HoverLiquid DropRipple on ClickCassette TapeLive BadgeMorph TriangleEqualiser Bars
View the full collection →