CSS :has() & Responsive Design

The CSS :has() pseudo-class was shipped for Safari in March 2022 and for Chromium browsers the same August. It's described on the Webkit blog as a way to "apply CSS to an element based on what’s happening inside that element" - so if I was to try and assign it some kind of pseudocode equivalent, an if contains.

The demands of responsive design by nature require flexibility, so the conditional qualities that :has() offers is a great addition to the CSS toolset and widens our options in aiming to achieve this kind of flexibility.

·

Towards the end of 2022 I was working on applying responsive design principles to the primary apps of a client suite of web apps.

In the interfaces, we have quite a few places where we have a heading aligned with some action buttons (shown here as the 'My latest devices' heading):

Current desktop header layout

The heading and buttons collectively make up our React component Header, which accepts both as props.

On mobile devices, this can end up looking pretty congested - text bunched up together, and tall buttons where the icon looks a little out of place.

Previous mobile header layout - crowded

Here’s what I tried at first in an effort to solve the problem:


Different button

I thought at first that we could make the buttons into a single ‘actions menu’ sort of button, before realizing that on some pages, there was a similar button just above. Having two lots of "actions" could be confusing, and even though on mobile, one would have to scroll down to see the second button and then the first one would be out of view; it wouldn't completely dispel the risk of things getting mixed up, and probably isn't best practice.

Two ‘Actions’ buttons wasn’t going to be as usable

Keeping these buttons, but just applying flex-direction: column so they would sit nicely underneath the heading

I then considered simply changing the main container for Header to use flex-direction: column for smaller screens, so the elements could all have some breathing room, like this:

Column layout gave multiple buttons some space

That also changed the headers that just had one button, like the one adjacent to the Actions button shown above. Perhaps this is not so bad, but it was not desired. An 'actions' menu aligned next to the heading seemed most suitable, even on a small screen.

Not as desired just for a single button

Desired

I also tried to implement a JavaScript solution where I added a class based on the conditions I was looking for, but the way that the header component and its props had been implemented meant it was difficult to avoid unnecessary complexity.

CSS :has() saves the day

After deciding CSS was probably the only way, I thought again about what I needed: a style that exists only where there are more than two buttons in the header. A style when a header only has two buttons.

There had been a lot of hype around CSS's :has() in my weekly dev newsletters, but even so, I wasn't sure in detail what it did - I wasn't sure it could solve this problem. After some Googling and going back to those articles, I learned that it was a "way to apply CSS to an element based on what’s happening inside that element."

At first-glance though, :has() didn't seem very complex, or powerful. I had questions I wanted :has() to answer: "Does my div have more than one button?", namely. Even though it's called :has() rather than :have, I was determined that it could answer this question for me, despite not seeing the examples that I needed immediately.

An explanatory video

Understanding CSS:has(), the top result when searching for "css has" on YouTube, really breaks down just what you can do with this seemingly humble pseudo-classs.

Towards the close of the video it presented some pretty unreadable code, but the use of the already familiar nth-child caught my eye.

The video presented a way to colour the background of alternative rows in a table:

tbody:has()(> tr:nth-child(7)) > tr:nth-child(odd) {
  background: silver;
}

Admittedly, it took me a moment to understand this code. What is happening here?

The tbody:has()(> tr:nth-child(7)) looks for the element which has a direct descendant (> being the child combinator) which fits the description tr:nth-child(7). In other words, it's looking for a table which has more than 6 rows. There's another child combinator after that to select any row which is an odd-number child, of that same table.

So essentially, highlight in silver any odd-numbered row that's a child of any table, as long as that table has more than 6 rows. This shows how powerful :has() really is, and how it can be combined with other features of the language to achieve this power.

How I applied that to my responsive design

As explained above, the criteria I wanted to apply when it came to ordering my header buttons underneath the heading text was "the header has (within it) more than one button". I could create that criteria with this code:

  .header:has()(button:nth-child(2)) {
    flex-direction: column;
    text-align: center;
  
    h2 {
      padding-bottom: $spacing-sm;
    }
  }

And create the design I desired once again.

Final mobile header layout

Concluding thoughts

On the WebKit blog, Jen Simmons writes, “It’s been a long-standing dream of front-end developers to have a way to apply CSS to an element based on what’s happening inside that element.” Using has() in the scenario above, avoiding either a more convoluted solution or a restricted design has allowed me to explore why this is such a helpful development. I imagine it will provide simpler solutions for responsive design problems and a variety of other styling obstacles for time to come.

Zurück
Zurück

Strategic Impact of Large Language Models

Weiter
Weiter

License Plate Detection for Precise Car Distance Estimation