Globally, the media control icons are some of the most universally understood visual language in any kind of interface. A designer can simply assume that every user not only knows ▶️ = play, but that users will seek out the icon in order to watch any video or animation.
Reportedly introduced in the 1960s by Swedish engineer Philip Olsson the play arrow was first designed to indicate the direction where the tape would go when reading on reel-to-reel tape players. Since then, we switched from cassettes to CDs, from the iPod to Spotify, but the media controls icons remain the same.
The play ▶️ icon is standard symbol (with its own unicode) of starting an audio/video media along with the rest of the symbols like stop, pause, fast-forward, rewind, and others.
There are unicode and emoji options for play button icons, but if you wanted something custom, you might reach for an icon font or custom asset. But what if you want to shift between the icons? Can that change be smooth? One solution could be to use SVG. But what if it could be done in 10 lines of CSS? How neat is that‽
In this article, we’ll build both a play button and a pause button with CSS and then explore how we can use CSS transitions to animate between them.
Play Button
Step one
We want to achieve a triangle pointing right. Let’s start by making a box with a thick border. Currently, boxes are the preferred base method to make triangles. We’ll start with a thick border and bright colors to help us see our changes.
<button class='button play'></button>
.button.play {
width: 74px;
height: 74px;
border-style: solid;
border-width: 37px;
border-color: #202020;
}

Step two
Rendering a solid color border yields the above result. Hidden behind the color of the border is a neat little trick. How is the border being rendered exactly? Let’s change the border colors, one for each side, will help us see how the border is rendered.
.button.play {
...
border-width: 37px 37px 37px 37px;
border-color: red blue green yellow;
}

Step three
At the intersection of each border, you will notice that a 45-degree angle forms. This is an interesting way that borders are rendered by a browser and, hence, open the possibility of different shapes, like triangles. As we’ll see below, if we make the border-left
wide enough, it looks as if we might achieve a triangle!
.button.play {
...
border-width: 37px 0px 37px 74px;
border-color: red blue green yellow;
}

Step four
Well, that didn’t work as expected. It is as if the inner box (the actual div) insisted on keeping its width. The reason has to do with the box-sizing
property, which defaults to a value of content-box
. The value content-box
tells the div to place any border on the outside, increasing the width or height.
If we change this value to border-box
, the border is added to the inside of the box.
.button.play {
...
box-sizing: border-box;
width: 74px;
height: 74px;
border-width: 37px 0px 37px 74px;
}

Final step
Now we have a proper triangle. Next, we need to get rid of the top and bottom part (red and green). We do this by setting the border-color
of those sides to transparent
. The width also gives us control over the shape and size of the triangle.
.button.play {
...
border-color: transparent transparent transparent #202020;
}

Here’s an animation to explain that, if that’s helpful.
Pause Button
Step one
We’ll continue making our pause symbol by starting with another thick-bordered box since the previous one worked so well.
<button class='button pause'></button>
.button.pause {
width: 74px;
height: 74px;
border-style: solid;
border-width: 37px;
border-color: #202020;
}

Step two
This time we’ll be using another CSS property to achieve the desired result of two parallel lines. We’ll change the border-style
to double
. The double property in border-style is fairly straightforward, doubles the border by adding a transparent stroke in between. The stroke or empty gap will be 33% of the given border width.
.button.pause {
...
border-style: double;
border-width: 0px 37px 0px 37px;
}

border-width property. Using the Final step
border-width
is what will make the transition work smoothly in the next step.
.button.pause{
...
border-width: 0px 0px 0px 37px;
border-color: #202020;
}

Animating the Transition
In the two buttons we created above, notice that there are a lot of similarities, but two differences: border-width
and border-style
. If we use CSS transitions we can shift between the two symbols. There’s no transition effect for border-style
but border-width
works great.
A pause
class toggle will now animate between the play and pause state.
Here’s the final style in SCSS:
.button {
box-sizing: border-box;
height: 74px;
border-color: transparent transparent transparent #202020;
transition: 100ms all ease;
will-change: border-width;
cursor: pointer;
// play state
border-style: solid;
border-width: 37px 0 37px 60px;
// paused state
&.pause {
border-style: double;
border-width: 0px 0 0px 60px;
}
}
Demo
See the Pen Button Transition with Borders by Chris Coyier (@chriscoyier) on CodePen.
Toggling without JavaScript
With a real-world play/pause button, it’s nearly certain you’ll be using JavaScript to toggle the state of the button. But it’s interesting to know there is a CSS way to do it, utilizing an input and label: the checkbox hack.
<div class="play-pause">
<input type="checkbox" value="" id="playPauseCheckbox" name="playPauseCheckbox" />
<label for="playPauseCheckbox"></label>
</div>
.playpause {
label {
display: block;
box-sizing: border-box;
width: 0;
height: 74px;
cursor: pointer;
border-color: transparent transparent transparent #202020;
transition: 100ms all ease;
will-change: border-width;
// paused state
border-style: double;
border-width: 0px 0 0px 60px;
}
input[type='checkbox'] {
visibility: hidden;
&:checked + label {
// play state
border-style: solid;
border-width: 37px 0 37px 60px;
}
}
}
Demo
See the Pen Toggle Button with Checkbox by Chris Coyier (@chriscoyier) on CodePen.
I would love your thoughts and feedback. Please add them in the comments below.
What is wrong with using U+25B6 ??
Also, please lets not try to think controls without JavaScript are a good thing. JavaScript increases accessibility. For example, without JavaScript you can not assign access keys (keyboard shortcuts) that allow keyboard users to control other aspects of the player when play (or whatever button) has focus. These users are forced to tab navigate sequentially to the button they need if there is not a keyup action event handler attached to the play button to perform the tasks of the other buttons.
Did you not read the article?
Actually, there is the accesskey attribute which can go on any element in HTML5. So that is still available to users.
Nothing! Although lots of unicode symbols render differently in the various browsers, so I personally don’t use them much. I know that websites don’t have to look the same in every browser, but with unicode symbols there’s quite often alignment differences between browser A and browser B and sometimes even in browser C, which can mess-up your UI..
This post actually reminds me of the early days of CSS Tricks. Chris himself wrote many of these kind of posts back then. It’s after all called CSS Tricks. Are these kind of tricks useful? Maybe! Are they practical? Sometimes!
It’s just having fun with CSS!
It’s not animatable.
As a JavaScript developer, I advocate writing as less JavaScript as possible.
It’s not something against JavaScript per se, is that code is itself a possible source of bugs. It holds for every language, of course, even CSS, but with just CSS your application doesn’t risk to break.
There’s the
accesskey
global attribute for that.I’m not saying you should not use JavaScript, but rather use it for things that are actually useful. For example, if you want the spacebar to toggle the playing status, that’s fine – there must be a rationale for everything.
Hi! Thanks for your comments.
There is nothing wrong in using the Unicode character or not using JavaScript to do the above code. There are cases to use them.
Why didn’t I use unicode? Shape was was abit different from what was requested from designers. You can’t (afaik) transition between other characters (like the pause one).
Why no JavaScript? Of course yes to JavaScript! The state (play/pause) can be bound to the checkbox, bind access key like you said, but, you don’t have to worrt about the Look of the button cause CSS will check the checked attribute and do it’s thing, with out JavaScript toggling any CSS class.
What this article tries to show is the power of CSS.
Daniel
And then on the contrary, if you rely on JavaScript and it doesn’t load (or isn’t allowed to load) then functionality is lost. You can keep focus on the checkbox hack because it’s an input in the DOM. Remember, there was a time when forms needed to operate without JavaScript and accessibility was baked in fairly well then.
“without JavaScript you can not assign access keys”…
Never heard about the HTML accesskey Attribute?
Not a bad idea when you need animation, otherwise unicode will be enough.
Here is my version
I also agree with Alice Wonder Miscreations, it’s not always good to use css for state change.
I made a variation on this, that uses pseudo elements (same border technique) so that the play icon splits in half when animating.
Nice article… really appreciate your breakdown in detail on making a triangle… love that demo animation, cleverly you use to made the speech buble for play again the demo.Big fan since the day I start learning CSS..
I would probably still rather use svgs instead of border-shapes because play.svg is easier to understand at a glance and modify than border-color: transparent transparent transparent #202020;
Putting aside the argument between CSS vs JavaScript this article was written very well and the explanations of the code was exemplary. Your way of breaking down the CSS was easy to understand and not snooty More people need to understand these things. I would have went straight to an SVG or icon font. Thanks.
It makes me sad to see how border hacks and pure CSS solutions are considered more important than good accessible interfaces. This “button” is not accessible from keyboard in both cases. Also, imagine toggling play/pause with a checkbox with no CSS applied. Sounds ridiculous, right? Why would you give this confusing checkbox metaphor to a screen reader user? Well, I know why: to make it CSS-only. But who on Earth cares if your JS player is toggled by CSS-only button?
I did this with pseudo elements after YouTube changed their button to have an animation. This one animates on hover but could easily be done with the checkbox hack.
https://codepen.io/fauxserious/details/XbvMrM/
That’s nice.
It is a clever and simple visual css hack. I like it. Eventually we can all use animatable clip-path for this, or someday maybe an SVG that handles the states and animation itself, but this works now and is pretty simple.
For a11y, you can add
role=button
,aria-label=“play/pause”
, andtabindex=0
to the label, and maybe a keypress handler, and you’d be pretty much good to go.The whole recommendation you gave (apart from
aria-label
) could be replaced by using proper<button>
element instead of<div>
: buttons automatically convert key events to clicks, have proper role and focusable by default.nice job.but,can you add a song that work with your button?
Thanks for inspiring article. I’ve explored the border styling property with some player buttons here : https://codepen.io/o-k-s-a-n-a/pen/YraoJV.
Love it – my mind blows when I think about how people can create all these awesome shapes in css and the transition is lovely – this truly is a CSS Trick
Nice interrobang use! Never really get to see those.
And now try to activate it with a keyboard :).
Nice
I did something similar a while ago
With the arrow splitting in half to reshape into the bars of the pause, like the YouTube button
Let me know what you think
Philip wrote in with a version using a CSS custom property so the size could be adjusted: