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!

☕️ Buy me a Coffee (€3)

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

JavaScript Temporal API — A Fix for the Date API

Anyone who has worked with dates and time in JavaScript knows how broken the built-in Date is. In the near future we’ll be able to use Temporal instead, an ECMAScript feature currently at Stage-3

💁‍♂️ Stage-3?

The Technical Committee which is concerned with the standardization of ECMAScript (e.g. TC39) has a 5 stage process in place, ranging from stage-0 to stage-4, by which it develops a new language feature.

Stage-3 is the Candidate Stage where the feature is considered complete and only critical changes will happen based on implementation experience. If all goes well the proposal will advance to Stage 4 without any changes, after which it will to become part of the ECMAScript Specification.

Nathan Sebhastian has a good overview on how it compares to Date, along with some practical examples.

The new Temporal API proposal is designed to solve the problems with the Date API. It brings the following fixes to JavaScript date/time manipulation:

  • Creating and dealing only with immutable Temporal objects
  • Straightforward API for date and time computations
  • Support for all timezones
  • Strict date parsing from ISO-8601 format
  • Support for non-Gregorian calendars

JavaScript Temporal API — A Fix for the Date API →

Animating Text Underlines

Instead of resorting to faux underlines using injected content, Michelle Barker shares that we nowadays can animate the text-decoration-* properties to achieve similar (and better) results.

This approach however won’t work in Chromium, as only Firefox/Safari support animating text-underline-offset at the moment … but thankfully a tiny amount of CSS Houdini Magic can be sprinkled on top to make Chromium happy 🤩

See the Pen
Underlines (Chrome solution with Houdini)
by Michelle Barker (@michellebarker)
on CodePen.

Animating Text Underlines →

Container Queries are Actually Coming / Say Hello To CSS Container Queries

In addition to my first look + demo at Container Queries, both Andy Bell and Ahmad Shadeed have also published posts covering them.

~

Ahmad starts off with explaining the idea behind them, before digging into a ton of use-cases. I especially like the pagination use-case, something I hadn’t thought of yet myself.

~

In his post, Andy taps into a — not yet existentcw unit, where 1cw equals 1% of the container. This comes in handy for tweaking the font-size based on the available space.

/* Before */
h1 {
  font-size: clamp(
    var(--fluid-type-min, 1rem),
    calc(1rem + var(--fluid-type-target, 3vw)),
    var(--fluid-type-max, 1.3rem)
  );
}

/* After */
h1 {
  font-size: clamp(
    var(--fluid-type-min, 1rem),
    calc(1rem + var(--fluid-type-target, 5cw)),
    var(--fluid-type-max, 1.3rem)
  );
}

Yes, we totally need this type of unit!

~

Container Queries are Actually Coming (by Andy) →
Say Hello To CSS Container Queries (by Ahmad) →

New in Chrome 90

Get your 90s vibe on, as Pete LePage has took the version number 90 to the next level there … BRILLIANT! 🤩

Chrome 90 is rolling out now! There’s a new value for the CSS overflow property. The Feature Policy API has been renamed to Permission Policy. And there’s a new way to implement and use Shadow DOM directly in HTML. Plus there’s plenty more.

~

The Declarative Shadow DOM seems like a very nice addition, but will to have to wrap my head around not using <shadow-root /> for it though — it’s <template shadowroot="open" /> we need to use. You can read the reasoning behind this decision in the explainer.

~

I’ve tweeted about overflow: clip; before. It can be compared to overflow: hidden;, except for the fact that it will never ever create a scrollcontainer (not even one that can be moved programmatically, through JavaScript).

~

New in Chrome 90 →

Comparing the New Generation of Build Tools

Over at CSS-Tricks Hugh Haworth has done a nice write-up on the next generation of build tools that have been popping up.

A bunch of new developer tools have landed in the past year and they are biting at the heels of the tools that have dominated front-end development over the last few years, including webpack, Babel, Rollup, Parcel, create-react-app.

Covered are esbuild, Snowpack, Vite, and wmr.

Comparing the New Generation of Build Tools →

Scroll-Linked Animations with CSS Scroll-Timeline (CSS Conf Colombia)

I just finished giving a lightning talk at the wonderful CSS Conf Colombia covering Scroll-Linked Animations with CSS @scroll-timeline. Really enjoyed giving a talk again after all this time, especially with this great vibe they have going on and the afterparty in gather.town (skribbl FTW!).

You can find the slides embedded below:

Source materials for the slides are my two posts on Scroll-Linked Animations:

  1. Part 1: Intro + Basic Scroll-Linked Animations
  2. Part 2: Scroll-Linked Animations with Element-Based Offsets

I’ll try to see if I can get a hold of the recording to embed here.

Thanks to the organisers for having me, and thanks to the attendees for watching me speak. I hope you all had fun attending this talk. I know I had making it (and whilst bringing it forward) 🙂

💁‍♂️ If you are a conference or meetup organiser, don’t hesitate to contact me to come speak at your event.

~

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!

☕️ Buy me a Coffee (€3)

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

Dark mode in 5 minutes, with inverted lightness variables

Lea Verou shows a method to implement dark mode, not by swapping entire colors, but by simply changing their lightness

The basic idea is to use custom properties for the lightness of colors instead of the entire color. Then, in dark mode, you override these variables with 100% - lightness. This generally produces light colors for dark colors, medium colors for medium colors, and dark colors for light colors, and still allows you to define colors inline, instead of forcing you to use a variable for every single color.

For best results she also taps into LCH colors.

LCH is a much better color space for this technique, because its lightness actually means something, not just across different lightnesses of the same color, but across different hues and chromas.

Dark mode in 5 minutes, with inverted lightness variables →

Accessible front-end components: claims vs reality

Great post by Hidde, warning about blindly trusting accessibility claims.

Not all ‘accessible components’ are created equal, some will work a lot better for our end users than other. In this post I have listed a number of things you can look at if you are considering third-party components.

I especially like this part:

Sometimes, HTML-only patterns are easier to understand for end users. More ARIA does not mean more accessibility.

It overlaps perfectly with Jeremy‘s thoughts in Robustness and Least Power

Accessible front-end components: claims vs reality →