Audio elements can be tricky to style. There is no straightforward way to style them, as applying CSS styles on audio directly does not work.

In this tutorial, we'll learn 2 ways of styling audios. We'll check how we can style audio elements with their pseudo-element selectors, then we'll see how we can style them completely from scratch.

This tutorial uses CodePens to show examples on the go. You can check out the full collection, as well.

The audio used in this tutorial is free audio from ZapSplat.

Using Pseudo-Element Selectors

Audio elements, by default, are not visible. You need to add the controls attribute for their controls to be visible.

This is how a basic audio player looks like:

Audio elements have the following pseudo-element selectors in CSS:

audio::-webkit-media-controls-panel
audio::-webkit-media-controls-mute-button
audio::-webkit-media-controls-play-button
audio::-webkit-media-controls-timeline-container
audio::-webkit-media-controls-current-time-display
audio::-webkit-media-controls-time-remaining-display
audio::-webkit-media-controls-timeline
audio::-webkit-media-controls-volume-slider-container
audio::-webkit-media-controls-volume-slider
audio::-webkit-media-controls-seek-back-button
audio::-webkit-media-controls-seek-forward-button
audio::-webkit-media-controls-fullscreen-button
audio::-webkit-media-controls-rewind-button
audio::-webkit-media-controls-return-to-realtime-button
audio::-webkit-media-controls-toggle-closed-captions-button

Using these selectors, you can give basic styling to audio elements. However, these only work on select browsers like Chrome.

We'll see a few examples of how we can use some of these selectors to style the audio element.

All the examples below will only work on Chrome. So, if you want to see how the audio element's style changes, please use Chrome.

Styling the Control Panel

To style the control panel, which is the container of all the audio's controls, you can use the selector audio::-webkit-media-controls-panel. In the example below, we use the selector to change the background color.

Styling the Mute Button

To style the mute button, you can use the selector audio::-webkit-media-controls-mute-button. In the example below, we change the background color of the mute button as well as add a border-radius.

Styling the Play Button

To style the play button, you can use the selector audio::-webkit-media-controls-play-button. In the example below, we change the background color of the play button as well as add a border-radius.

Style the Current Time

To style the current time you can use the selector audio::-webkit-media-controls-current-time-display. In the example below, we change the color of the text.

Style the Remaining Time

To style the remaining time you can use the selector audio::-webkit-media-controls-time-remaining-display. In the example below, we change the color of the text.

Style the Timeline

To style the timeline you can use the selector audio::-webkit-media-controls-timeline. In the example below, we add a background color and a border radius.

Styling the Volume Slider

To style the volume slider, which on Chrome appears after hovering the mute button, you can use the selector audio::-webkit-media-controls-volume-slider. In the example below, we add a background color, a border radius, and some padding.

Styling a Custom Audio Player

In this section, we'll create our own custom audio player. We'll a nice looking player that uses different elements to achieve a good style. Then with the help of Javascript bind the audio element's functionalities to these elements.

All the icons used in this section are from Heroicons.

You can see the full demo on CodePen at the end of the section.

Create the Track Image

Usually, audio players have an image of the track playing. It gives a nice style to the audio player. We'll just an icon from Heroicons to simulate that.

We'll start by adding in the HTML the container .audio-player. Inside that container, we'll add the track "image" element.

<div class="audio-player">
  <div class="icon-container">
    <svg xmlns="http://www.w3.org/2000/svg" class="audio-icon" viewBox="0 0 20 20" fill="currentColor">
  <path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
</svg>
       <audio src="https://www.zapsplat.com/wp-content/uploads/2015/sound-effects-61905/zapsplat_multimedia_alert_chime_short_musical_notification_cute_child_like_001_64918.mp3?_=1"></audio>
  </div>
</div>

Then, we'll add some CSS to style these elements.

.audio-player {
  width: 15rem;
  height: 15rem;
}

.icon-container {
  width: 100%;
  height: 100%;
  background-color: #DE5E97;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
}

.audio-icon {
   width: 90%;
   height: 90%;
}

This will create the following audio track image.

This has nothing to do with the actual functionality of the audio. It's just to make the visual nice.

Add the Play Button

Next, we'll add the play button. There are 3 phases of adding the play button: adding the HTML elements, adding the CSS styling, then implementing the Javascript functionality.

Add the HTML elements

Add the following inside the .audio-player element:

<div class="controls">
    <button class="player-button">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
</svg>
    </button>
</div>

This will add a container .controls element, then inside it, we're adding a button that has a play icon inside.

Add the CSS styles

Next, we'll add the CSS styles for the .controls element and the button.

First, add the following CSS Variable inside .audio-player:

.audio-player {
  --player-button-width: 3em;
    ...
}

Then, add the following CSS to style the .controls and .player-button elements:

.controls {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
  margin-top: 10px;
}

.player-button {
  background-color: transparent;
  border: 0;
  width: var(--player-button-width);
  height: var(--player-button-width);
  cursor: pointer;
  padding: 0;
}

This will style the .controls element to be a flexbox element. This will allow us to align the controls (which we will add more later) inside nicely.

The player button just has a transparent background and no border, as we just want to show the icon inside.

This will produce the following UI:

However, clicking the button now does nothing. We need to use Javascript to bind the functionalities to the audio.

Bind the Functionality with Javascript

In Javascript, we'll first define some variables:

const playerButton = document.querySelector('.player-button'),
      audio = document.querySelector('audio'),
      playIcon = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
    <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
  </svg>
      `,
      pauseIcon = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
      `;

We've defined playerButton which is the player button element, audio which is the audio this player is for, playIcon and pauseIcon which we will use to toggle the icon of the button.

Then, we'll create the function that should be triggered when the button is clicked:

function toggleAudio () {
  if (audio.paused) {
    audio.play();
    playerButton.innerHTML = pauseIcon;
  } else {
    audio.pause();
    playerButton.innerHTML = playIcon;
  }
}

This function checks whether the audio is paused or playing, then it either plays or pauses it. It also changes the icon of playerButton.

Next, add the function as an event listener to the click event of playerButton:

playerButton.addEventListener('click', toggleAudio);

We also need to update the icon of playerButton when the audio ends. To do that, we can use the audio element's event ended and in the listener change the icon back to the play icon:

function audioEnded () {
  playerButton.innerHTML = playIcon;
}

audio.onended = audioEnded;

You can try to play the audio by clicking on the play button, and the audio will work!

Add the Timeline Track

Next, we need to add the timeline track, which will allow us to see the progress of the audio, as well as seek through the audio.

To implement the audio timeline track, the easiest approach is to use a range input. Using the range input, we'll first style it with CSS, then bind the functionalities in Javascript.

Add the HTML elements

Inside .controls add the following input range:

<input type="range" class="timeline" max="100" value="0">

Add the CSS styles

To style a range input, there are two elements to take into account: the thumb, which allows us to change the value of the input, and the track that the thumb resides on.

To style the thumb, the following cross-browser selectors are used:

::-webkit-slider-thumb
::-moz-range-thumb
::-ms-thumb

and the following cross-browser selectors are used to style the track:

::-webkit-slider-runnable-track 
::-moz-range-track
::-ms-track

For the simplicity of this tutorial and to avoid repetition, we'll just show the code for -webkit selectors. You can find the full, cross-browser code in the demo CodePen.

We'll first style the input range itself:

.timeline {
  -webkit-appearance: none;
  width: calc(100% - var(--player-button-width));
  height: .5em;
  background-color: #e5e5e5;
  border-radius: 5px;
  background-size: 0% 100%;
  background-image: linear-gradient(#DE5E97, #DE5E97);
  background-repeat: no-repeat;
}

Using -webkit-appearance: none; is necessary to be able to apply the styling.

Using linear-gradient(#DE5E97, #DE5E97); for background-image allows us to easily add the progress track of a different color based on the current progress of the audio.

To change the size of the background image, which means the position of the current progress in the audio, we use background-size: 0% 100%;. The first value is the width. It will be the value we'll update through Javascript to show the progress of the audio.

Next, we'll add the styling of the thumb:

.timeline::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: all .1s;
  background-color: #a94672;
}

.timeline::-webkit-slider-thumb:hover {
  background-color: #943f65;
}

.timeline:hover::-webkit-slider-thumb {
  opacity: 1;
}

We're just adding some styling to the thumb, and we're hiding it and showing it on hover.

Then, we'll basically hide the track as we'll just use the styling in .timeline to show the track and the progress of the audio:

.timeline::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  box-shadow: none;
  border: none;
  background: transparent;
}

The track will look like this:

Bind the Javascript Functionality

We just need to add the Javascript functionality now. The track should show the progress of the audio, and it should allow changing the progress of the audio by moving the thumb.

First, we'll define the timeline variable for the element:

const timeline = document.querySelector('.timeline');

Then, we'll add the function that will listen to the timeupdate event. The timeupdate event is triggered whenever the audio's time changes. So, it's triggered continuously as the audio is playing, and it's triggered when the audio currentTime attribute is updated.

The function will calculate the progress of the audio in percentage using audio's currentTime attribute and audio's duration attribute. Then, will set the backgroundSize CSS property of the timeline element based on the calculation:

function changeTimelinePosition () {
  const percentagePosition = (100*audio.currentTime) / audio.duration;
  timeline.style.backgroundSize = `${percentagePosition}% 100%`;
  timeline.value = percentagePosition;
}

audio.ontimeupdate = changeTimelinePosition;

Next, we need to add the function that will handle the change event of the input range button, then change the progress of the audio as well as the backgroundSize CSS property:

function changeSeek () {
  const time = (timeline.value * audio.duration) / 100;
  audio.currentTime = time;
}

timeline.addEventListener('change', changeSeek);

You can now play the audio and see how the track shows the progress of the audio. You can also try changing the progress by moving the thumb.

Add the Sound Button

The last thing we'll do is add a sound button. This button will just toggle the sound of the audio, muting and unmuting it.

Add the HTML elements

Add the following HTML elements inside .controls:

<button class="sound-button">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
  <path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clip-rule="evenodd" />
</svg>
    </button>

This is just a button with an icon.

Add the CSS Styles

Next, we need to add the CSS styles.

First, add 2 new variables inside .audio-player:

.audio-player {
  --player-button-width: 3em;
  --sound-button-width: 2em;
  --space: .5em;
    ...
}

The --sound-button-width will be used for the width of the sound button, and --space will be used to add space between the track and the button.

Next, change the width of the .timeline element and add a margin-right property as well:

.timeline {
    width: calc(100% - (var(--player-button-width) + var(--sound-button-width) + var(--space)));
margin-right: var(--space);
    ...
}

Finally, add the CSS styling for the sound button:

.sound-button {
  background-color: transparent;
  border: 0;
  width: var(--sound-button-width);
  height: var(--sound-button-width);
  cursor: pointer;
  padding: 0;
}

We'll now have a sound button next to the track:

Bind the Javascript Functionality

Lastly, we just need to bind the functionality of the sound button to the audio element. Clicking the sound button should mute or unmute the sound of the audio.

First, add the following new variable definitions:

const soundButton = document.querySelector('.sound-button'),
      soundIcon = `
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
  <path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clip-rule="evenodd" />
</svg>`,
      muteIcon = `
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#3D3132">
  <path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>`;

This will add a soundButton variable, which will be the sound button element. It will also create two variables soundIcon and muteIcon to be used to change the icon of the button based on whether the audio is muted or not.

Next, add the function which will listen to the click event on the sound button:

function toggleSound () {
  audio.muted = !audio.muted;
  soundButton.innerHTML = audio.muted ? muteIcon : soundIcon;
}

soundButton.addEventListener('click', toggleSound);

Final Demo

This will be the final result of creating the custom audio player:

The player can play, pause, seek, mute and unmute the audio, all while looking great.

Conclusion

Using the pseudo-element selectors, you can do simple design changes to the audio element.

For more complex design changes, it's best to implement a custom audio player, then bind it with Javascript to the audio element to provide the necessary functionalities.