Identify and Extract Pseudo-Element Selectors from built-in HTML Elements using DevTools

Recently Stefan Judis shared how to style the browse button of a file selector using the ::file-selector-button pseudo-element

But what about other complex elements in the browser? How can we tweak individual parts of those? Take <audio> for example: is there a pseudo-element we can use to style the play button?

~

Table of Contents

  1. Dissecting <input type="file" />
  2. How to use DevTools to peek inside <input type="file" />
  3. Dissecting <audio>
  4. Is there a catch?
  5. One last thing

~

# Dissecting <input type="file" />

Before we answer the question above, let’s first take a look at the <input type="file" /> Stefan used. Below is screenshot of how it’s rendered in Chromium on Mac, with a few extra outlines added.

Even though we only type 1 element in our HTML code, we see it consists of two parts:

  1. A β€œChoose File” button
  2. A label reading β€œNo file chosen”

Internally, this is also how the browser builds it. We type in:

<input type="file" />

But what’s being rendered is this:

<input type="button" value="Choose file" pseudo="-webkit-file-upload-button" id="file-upload-button">
<span aria-hidden="true">No file chosen</span>

(I’ll show you further down this post how I know this πŸ˜‰)

The ::file-selector-button selector Stefan mentioned targets only the <input type="button" …> you see there. Using it you can style the button, like he did.

🧐 If you’re paying close attention you might notice that the pseudo-element used is ::file-selector-button, whilst the pseudo attribute of the button reads -webkit-file-upload-button. More on that further down the post πŸ˜‰

~

# How to use DevTools to peek inside <input type="file" />

In the Chromium DevTools we don’t get to see the two elements that make up <input type="file" />. That’s because this information as is hidden in the Shadow DOM.

Thankfully there is a way to have DevTools show them. To do so, open DevTools’ Settings, and under Elements check the option that reads β€œShow user agent Shadow DOM”.

🦊 Firefox or 🧭 Safari user? The DevTools in Firefox/Safari have this option enabled out of the box.

Once enabled you’ll see #shadow-root (user-agent) appear in the Elements Tree for all elements that are built that way (and there quite a few!).

<input type="file">
  ↳ #shadow-root (user-agent)
      <input type="button" value="Choose file" pseudo="-webkit-file-upload-button" id="file-upload-button">
      <span aria-hidden="true">No file chosen</span>
</input>

☝️ Having this option enabled all the time can be quite distracting. I personally only turn it on when I need it.

~

# Dissecting <audio>

Winging back to our initial question β€œis there a pseudo-element we can use to style the play button?”, we can use the DevTools to see the underlying structure.

In Chromium we get back this structure:

<audio controls="" src="/media/cc0-audio/t-rex-roar.mp3">
  ↳ #shadow-root (user-agent)
    <div pseudo="-webkit-media-controls" class="phase-ready state-stopped">
      <div pseudo="-webkit-media-controls-overlay-enclosure">
        <input pseudo="-internal-media-controls-overlay-cast-button" type="button" aria-label="play on remote device" style="display: none;">
      </div>
      <div pseudo="-webkit-media-controls-enclosure">
        <div pseudo="-webkit-media-controls-panel">
          <input type="button" pseudo="-webkit-media-controls-play-button" aria-label="play" class="pause" style="">
          <div aria-label="elapsed time: 0:00" pseudo="-webkit-media-controls-current-time-display" style="">0:00</div>
          <div aria-label="total time: / 0:02" pseudo="-webkit-media-controls-time-remaining-display" style="">/ 0:02</div>
          <input type="range" step="any" pseudo="-webkit-media-controls-timeline" max="2.115918" aria-label="audio time scrubber 0:00 / 0:02" aria-valuetext="elapsed time: 0:00">
          <div pseudo="-webkit-media-controls-volume-control-container" class="closed" style="">
            <div pseudo="-webkit-media-controls-volume-control-hover-background"></div>
            <input type="range" step="any" max="1" aria-valuemax="100" aria-valuemin="0" aria-label="volume" pseudo="-webkit-media-controls-volume-slider" aria-valuenow="100" class="closed" style=""><input type="button" pseudo="-webkit-media-controls-mute-button" aria-label="mute" style="">
          </div>
          <input type="button" pseudo="-webkit-media-controls-fullscreen-button" aria-label="enter full screen" style="display: none;">
          <input type="button" aria-label="show more media controls" title="more options" pseudo="-internal-media-controls-overflow-button" style="">
        </div>
      </div>
      <div role="menu" aria-label="Options" pseudo="-internal-media-controls-text-track-list" style="display: none;"></div>
      <div pseudo="-internal-media-controls-overflow-menu-list" role="menu" class="closed" style="display: none;">
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label=" Play " style="display: none;">
          <input type="button" pseudo="-webkit-media-controls-play-button" tabindex="-1" aria-label="play" class="pause" style="display: none;">
          <div aria-hidden="true">
            <span>Play</span>
          </div>
        </label>
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label="enter full screen Full screen " style="display: none;">
          <input type="button" pseudo="-webkit-media-controls-fullscreen-button" aria-label="enter full screen" tabindex="-1" style="display: none;">
          <div aria-hidden="true">
            <span>Full screen</span>
          </div>
        </label>
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label="download media Download " class="animated-0" style="">
          <input type="button" aria-label="download media" pseudo="-internal-media-controls-download-button" tabindex="-1" style="">
          <div aria-hidden="true">
            <span>Download</span>
          </div>
        </label>
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label=" Mute " class="animated-2" style="display: none;">
          <input type="button" pseudo="-webkit-media-controls-mute-button" tabindex="-1" aria-label="mute" style="display: none;">
          <div aria-hidden="true">
            <span>Mute</span>
          </div>
        </label>
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label="play on remote device Cast " class="animated-1" style="display: none;">
          <input pseudo="-internal-media-controls-cast-button" type="button" aria-label="play on remote device" tabindex="-1" style="display: none;">
          <div aria-hidden="true">
            <span>Cast</span>
          </div>
        </label>
        <label pseudo="-internal-media-controls-overflow-menu-list-item" role="menuitem" tabindex="0" aria-label="show closed captions menu Captions " class="animated-0" style="display: none;">
          <input aria-label="show closed captions menu" type="button" pseudo="-webkit-media-controls-toggle-closed-captions-button" tabindex="-1" style="display: none;">
          <div aria-hidden="true">
            <span>Captions</span>
          </div>
        </label>
      </div>
    </div>
</audio>

Or visually, with outlines added:

With a bit of digging we can find the the play button on line #9, and extract its pseudo attribute.

<input type="button" pseudo="-webkit-media-controls-play-button" aria-label="play" class="pause" style="">

πŸ‘‰ To style the play button we can use ::-webkit-media-controls-play-button

~

# Is there a catch?

While the ::-webkit-media-controls-play-button selector above works, there’s a catch though: it only works in Chromium based browsers, and this for several reasons:

  1. Every browser engine has its own implementation for what makes up an <audio> element. Shown below is a comparison of the UI for the <audio> as seen in Firefox, Chromium, and Safari.

    Whilst all implementation contain a play button, not all β€” for example β€” contain an element that indicates the current time. Styling that wouldn’t be possible.

  2. Not all browser expose the same parts of the UI using pseudo-elements. When it comes to <audio> for example, only Chromium exposes parts of its UI. Firefox and Safari don’t expose any pseudo-element for <audio>

    And even if they would, the wouldn’t use ::-webkit-media-controls-play-button for it.

Another aspect to take into account is that ::-webkit-media-controls-play-button is something that Chromium decided to use. This wasn’t discussed with any other browser vendor. As Thomas Steiner warns:

~

# One last thing

To close off I still owe you an explanation to why Chromium lists ::-webkit-file-upload-button for the browse button of <input type="file" />, instead of ::file-selector-button.

This is because they first exposed it using their own internal ::-webkit-file-upload-button name. It was only later that the CSS Working Group decided to standardize it to ::file-selector-button.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

BuymeaCoffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.