To use Tailwind CSS or not to use Tailwind CSS: that is not the question we're going to answer in this blog post. We’re going to use it, because we need to get things done and preferably fast!
That’s one of many benefits Tailwind users enjoy, quickly prototyping designs by writing only HTML. My colleague Darius knows what I’m talking about.
In this post, we’re going to build a custom audio player with Tailwind CSS and Media Chrome, an open-source library that helps you build media players using Web Components. You'll also get to play with some of the web's most fun features, like CSS custom properties, SVG symbol maps, and even shiny new container queries.
The design can be seen on the tailwindui.com home page at the time of writing. It should look a little like the image below when we’re finished.
Media Chrome is a set of Web Components that make it really easy to build an audio or video player. Media players are one of the more complex parts of a web page, even the UI of the player can be challenging to get right.
There are a lot of media states to keep in sync with the UI and handling the user input can get especially tricky between different browsers and input types from mobile and desktop devices. Get used to edge cases!
Making UI components accessible is often unfortunately an afterthought, in Media Chrome all components get the right accessibility features out of the box. Aria attributes like aria-label, aria-hidden, aria-expanded, etc. and intuitive keyboard navigation.
There’s plenty more to cover why Media Chrome helps you smooth over a lot of pain points compared to slapping a few styled divs on the page but that’s not the purpose of this article.
We’ll start out by defining the HTML structure of the audio player and cherry-picking the needed controls in the right order.
Before we can write out the custom HTML elements we add the script tag with the Media Chrome library. jsDelivr automatically bundles and minifies the library’s ESM modules if you add /+esm at the end of the URL.
The next required element is <media-controller>. We’ll not go into the specifics right now, but you can think of it as a container holding the media element and the UI controls and facilitates the communication between those two. The audio attribute makes sure no media display elements are shown and sets up media chrome appropriately for an audio player.
The only special syntax on the <audio> element is the slot attribute which is a Web Component feature to slot specific elements from the normal DOM into a place in the element’s shadow DOM.
The <media-control-bar> holds all the controls which are laid out in the same order in which they occur in the audio player design.
So far the result should look a little like this:
The default styles are quite minimal and let’s say functional. That’s intentional, it’s meant to be styled and made your own. Let’s do that!
Before we start blasting our project with Tailwind, there’s one quick change we have to make — put in icons that match the design. Luckily, these are icons that are freely available at heroicons.com. We can copy/paste the six icons we need into SVG symbols, which keeps the markup clean by separating the SVG content and the Media Chrome HTML.
The <svg> element gets a hidden class to hide the symbols map. Each symbol in the map gets a unique id which we can use later as a reference in the <use> tag.
The new SVG icons can be used by referencing the needed symbol like so: <svg><use href=”#idref” /></svg>. Each SVG element gets a slot attribute which tells the browser to project the custom SVG icon in the button’s icon slot, replacing the default icon. The aria-hidden=”true” attribute removes the SVG element from the accessibility tree so it doesn’t get in the way of assistive technology. Finally, we can start adding some Tailwind classes to style the icons to match the audio player. A fixed width and height, a white fill, and a white stroke will do for this iteration.
With the HTML structure and the icons out of the way, let’s start styling! The most obvious thing we should change is the background color. Media Chrome sports a black background color by default and the new design has a white background.
It’s possible to change the background color of each control separately with Tailwind but Media Chrome has some low-level CSS vars (also known as CSS custom properties) that can do that in one go by defining it on a parent element — in this case, the media-controller element. This will save us from adding the duplicate markup we’d need if we did this for each element separately. Below you can see we set the CSS vars in the style attribute to transparent to reset the media and control background colors.
Next up is the control bar. We give it a fixed height of h-16, which translates to 4rem . By default 1rem, in most browsers, is 16px which gives the control bar a height of 64px. px-4 adds 1rem of horizontal padding. In addition, we added a white background, vertically aligned the controls, rounded corners, and added a 1px slate-colored border and a shadow (bg-white item-center rounded-md ring-1 ring-slate-700/10 shadow-xl). That’s it!
In the design, the play button is like a punched-out circle. These classes h-10 w-10 p-2 mx-3 rounded-full bg-slate-700 on the play button give it a fixed width and height of 2.5rem, a 0.5rem padding, a 0.75rem horizontal spacing, fully rounded corners, and a slate-colored background. The relative left-px offsets the play button 1px to the right so the geometric centers of both shapes align better.
The rest of the button icons are given the slate-colored variant for the fill and stroke instead of white (fill-slate-500, stroke-slate-500).
Finally, the time range is given a fixed height, rounded corners, and a light slate color. The min-h-0 sets the minimum height to zero to override the default auto value that flexbox applies to its content. If it wasn’t set to zero, setting the fixed height would not have any effect.
The time range is more complex because it consists of several inner elements like a track element, a progress bar element, a buffered bar element, a thumb element, etc. Since this HTML structure is part of the element's shadow DOM it can’t be styled from outside of the element with Tailwind. Here we’re required to use one of Media Chrome’s styling strategies like CSS vars.
Going over the inline style rules from top to bottom:
--media-range-track-background: transparent; Sets the track background transparent because the background color was already set on media-time-range.
--media-time-range-buffered-color: rgb(0 0 0 / 0.02); This makes the buffered progress bar element black with an opacity of 2%.--media-range-bar-color: rgb(79 70 229); Sets the background color of the playback progress bar to a blueish-purple color.
--media-range-track-border-radius: 4px; Makes the corners of the time range rounded.
--media-range-track-height: 0.5rem; Sets the height of the internal track element.
The rest is pretty self-explanatory, except for the following:
--media-range-thumb-box-shadow: 0 0 0 2px rgb(255 255 255 / 0.9); This uses the box-shadow property of the range thumb to create a gap between the thumb and the progress bar.
All this together should result in something like the following image.
On a mobile device (or in a small container) the time range will probably get pretty short. Typically, it’s moved to the top of the control bar above the other controls. This is not too hard to implement with container queries and Tailwind. Container queries are relatively new but they are now available in all major browsers. Container queries enable you to apply styles to an element based on the size of the element's container. They differ from media queries which are based on the size of the browser window.
First, we need to add the @container class to the wrapping container. In our case, this is the media-controller element. Then we can add the classes block @md:hidden to elements that only need to be displayed on mobile and add the classes hidden @md:block on the elements for larger containers.
All these examples can be found in this Codesandbox.
The final touch is creating a Media Chrome theme for easy distribution. It’s as simple as wrapping the HTML in a <template> element, giving it a unique id attribute, and replacing the <audio> element with a <slot name="media" slot="media"></slot>.
After that we can render the theme by using the <media-theme> element, setting the template attribute to the chosen id and adding the <audio> element as the media slot.
This post just scratches the surface when it comes to Media Chrome themes. Dive into the docs to learn more. Themes can be used across different players, including Mux Player.
Let us know if you created your own theme. We’d love to see it!
Coder, mnmalist, perf junkie and big into UI and video tech. Austinite originally from Belgium who loves exploring and biking. Jumping sheep hills once in a while at 9th Street BMX Park.