3 Pure CSS4 with JSPublished 2026-10-05Free · no sign-up
Seven media controls: a play button that twists into pause, a podcast button with a progress ring, a frosted play button over a video poster, a mini audio player, a CSS-only toggle built on a checkbox, a live stream button with a pulsing halo and a track button with equaliser bars.
One button toggles between a play triangle and two pause bars: both icons sit in the same grid cell and swap with a short rotate-and-fade. The aria-label switches between Play and Pause so the button always names what it will do next.
var b = document.querySelector('.pp1');
b.addEventListener('click', function () {
var on = b.classList.toggle('on');
b.setAttribute('aria-label', on ? 'Pause' : 'Play');
});
02. Play Button with Progress Ring
MediaProgress
A podcast-style play button whose ring fills as the episode plays. The ring is an SVG circle whose stroke-dashoffset is set from the elapsed time, pausing stops the pretend playback timer, and the elapsed time is written out in text beside the button.
var b = document.querySelector('.pp2 button'), t = document.querySelector('.pp2 .time'), v = b.querySelector('.v'), ic = b.querySelector('.ic path'), s = 0, id = null;
function draw() { v.style.strokeDashoffset = 100 - s / 30 * 100; t.textContent = '0:' + String(s).padStart(2, '0') + ' / 0:30'; }
function stop() { clearInterval(id); id = null; ic.setAttribute('d', 'M8 5l11 7-11 7z'); b.setAttribute('aria-label', 'Play episode'); }
b.addEventListener('click', function () {
if (id) { stop(); return; }
if (s >= 30) { s = 0; draw(); }
ic.setAttribute('d', 'M7 5h3.5v14H7zM13.5 5H17v14h-3.5z'); b.setAttribute('aria-label', 'Pause episode');
id = setInterval(function () { s++; draw(); if (s >= 30) stop(); }, 1000);
});
draw();
03. Video Overlay Play Button
MediaVideo
A large translucent play button centred over a video poster, with a frosted backdrop so it reads on any image. The poster here is a CSS gradient stand-in. The button is a real button inside the figure, and its name says which video it plays.
View code
<figure class="pp3"><div class="poster" aria-hidden="true"></div><button aria-label="Play video: Glazing a bowl in 60 seconds"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5l11 7-11 7z"/></svg></button><figcaption>Glazing a bowl in 60 seconds</figcaption></figure>
A compact player with previous, play or pause and next buttons, a track title and a progress bar. Each icon button has an aria-label, the play button toggles aria-pressed, and progress is a native progress element with its own label.
var p = document.querySelector('.pp4 .play'), path = p.querySelector('path');
p.addEventListener('click', function () {
var on = p.getAttribute('aria-pressed') !== 'true';
p.setAttribute('aria-pressed', on); p.setAttribute('aria-label', on ? 'Pause' : 'Play');
path.setAttribute('d', on ? 'M7 5h3.5v14H7zM13.5 5H17v14h-3.5z' : 'M8 5l11 7-11 7z');
});
05. CSS-Only Play Toggle
MediaNo JS
A play and pause toggle with no JavaScript: a visually hidden checkbox inside a label, with the icons switched by :checked. The checkbox is named Playing, so screen readers announce a checkbox that is checked or not checked, and :has() draws the focus ring.
A play button with a LIVE label and a soft pulsing halo that tells people the stream is happening now. The halo is a scaled pseudo-element that fades as it grows, and the visible text Play live stream is the accessible name.
While playing, the icon becomes three moving equaliser bars, a familiar now-playing signal in track lists. Pausing freezes the bars with animation-play-state rather than removing them, and the button text names the track so a list of these buttons stays distinct.
var b = document.querySelector('.pp7');
b.addEventListener('click', function () { b.setAttribute('aria-pressed', b.getAttribute('aria-pressed') === 'true' ? 'false' : 'true'); });
About these CSS Play / Pause Buttons
Name the next action
An icon-only media button needs an accessible name, and the name should change with the state: Play when paused, Pause when playing. Alternatively keep one name and toggle aria-pressed.
Icons as SVG
Simple SVG paths keep the icons sharp at any size and colourable with fill: currentColor or a set fill. Two icons stacked in the same grid cell can cross-fade without either one shifting the layout.
Big enough to hit
Play buttons are often the main control on a card or video, so make them large: at least 44 by 44 pixels, and bigger over video, where the poster image gives plenty of room.
Wire them to real media
Connect the button to an <audio> or <video> element by calling play() and pause(), and update the icon from the element's play and pause events so it stays in sync with the keyboard and system controls.
Frequently asked questions
Do these buttons play audio?
No. They show the control states with pretend timers; connect them to your own media element.
Can I make the toggle without JavaScript?
Yes. The CSS-only demo uses a hidden checkbox and :checked, but real playback still needs script.
Why aria-pressed on some and aria-label on others?
Both work. aria-pressed keeps one name and reports on or off; changing the label says the next action.