Playing Sounds with CSS

Avatar of Alvaro Montoro
Alvaro Montoro on

CSS is the domain of styling, layout, and presentation. It is full of colors, sizes, and animations. But did you know that it could also control when a sound plays on a web page?

This article is about a little trick to pull that off. It’s actually a strict implementation of the HTML and CSS, so it’s not really a hack. But… let’s be honest, it’s still kind of a hack. I wouldn’t recommend necessarily using it in production, where audio should probably be controlled with native <audio> elements and/or JavaScript.

The trick

There are a few alternatives to playing sounds with CSS, but the underlying idea is the same: inserting the audio file as a hidden object/document within the web page, and displaying it whenever an action happens. Something like this:

<style>
  embed { display: none; }
  button:active + embed { display: block; }
</style>

<button>Play Sound</button>
<embed src="path-to-audio-file.mp3" />

View Demo

This code uses an <embed> tag, but we could also use <object> with similar results:

<object data="path-to-audio-file.mp3"></object>

A quick note on the demo and this technique. I developed a small piano on CodePen just with HTML and CSS using this technique about a year ago. It worked great, but since then, some things have changed and the demo doesn’t work on CodePen anymore.

The biggest change was related to security. As it uses embed or object instead of audio, the imported file is subject to stricter security checks. Cross-origin access control policies (CORS) force the audio file to be on the same protocol and domain as the page it is imported into. Even putting the sound in base64 will not work anymore. Also, you (and users) may need to activate autoplay on their browser settings for this trick to work. It is often enabled behind a flag.

Another change is that browsers now only play the sounds once. I could have sworn that past browsers played the sound every time that it was shown. But that doesn’t appear to be the case anymore, which considerably limits the scope of the trick (and bares the piano demo almost useless).

The CORS issue can be worked around if you have control over the servers and files, but the disabled autoplay is a per-user thing that is out of our control.

View Demo

Why it works

The theory behind this behavior can be found buried in the definition of the embed tag:

Whenever an embed element that was not potentially active becomes potentially active, and whenever a potentially active embed element that is remaining potentially active and has its src attribute set, changed, or removed or its type attribute set, changed, or removed, the user agent must queue a task using the embed task source to run the embed element setup steps for that element.

Same goes for the definition of the object tag:

Whenever one of the following conditions occur:

[…]

  • the element changes from being rendered to not being rendered, or vice versa,

[…] the user agent must queue a task to run the following steps to (re)determine what the object element represents. [and eventually process and run it]

While it is clearer for object (the file is processed and run on render), we have this concept of “potentially active” for embed that may seem a bit more complicated. And while there are some additional conditions, it will run on initial render similarly as how it does with object.

As you can see, technically this is not a trick at all, but how all browsers should behave… although they don’t.

Browser support

As with many of these hacks and tricks, the support of this feature is not great and varies considerably from browser to browser.

It works like a charm on Opera and Chrome, which means a big percentage of the browser market. However, the support is spotty for other Chromium-based browsers. For example, Edge on Mac will play the audio correctly, while the Brave browser won’t unless you have the latest version.

Safari was a non-starter, and the same can be said for Internet Explorer or Edge on Windows. Nothing close to working on any of these browsers.

Firefox will play all the sounds at once on page load, but then won’t play them after they are hidden and shown again. It will trigger a security warning in the console as the sounds attempt to be play “without user interaction,” blocking them unless the user approves the site first.

Overall, this is a fun trick with CSS but one of those “don’t do this at home” kind of things… which in software development, means this is the perfect thing to do at home. 🙂