Geoff Graham – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Thu, 11 Jul 2024 16:17:13 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 Geoff Graham – CSS-Tricks https://css-tricks.com 32 32 45537868 “If” CSS Gets Inline Conditionals https://css-tricks.com/if-css-gets-inline-conditionals/ https://css-tricks.com/if-css-gets-inline-conditionals/#comments Tue, 09 Jul 2024 15:18:11 +0000 https://css-tricks.com/?p=379002 A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that …


“If” CSS Gets Inline Conditionals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that caught my attention:

Lea is the one who opened the GitHub issue leading to the discussion and in a stroke of coincidence — or serendipity, perhaps — the resolution came in on her birthday. That had to be quite a whirlwind of a day! What did you get for your birthday? “Oh, you know, just an accepted proposal to the CSS spec.” Wild, just wild.

The accepted proposal is a green light for the CSSWG to work on the idea with the intent of circulating a draft specification for further input and considerations en route to, hopefully, become a recommended CSS feature. So, it’s gonna be a hot minute before any of this is baked, that is, if it gets fully baked.

But the idea of applying styles based on a conditional requirement is super exciting and worth an early look at the idea. I scribbled some notes about it on my blog the same day Lea posted to X and thought I’d distill those here for posterity while rounding up more details that have come up since then.

This isn’t a new idea

Many proposals are born from previously rejected proposals and if() is no different. And, indeed, we have gained several CSS features in recent days that allow for conditional styling — :has() and Container Style Queries being two of the more obvious examples. Lea even cites a 2018 ticket that looks and reads a lot like the accepted proposal.

The difference?

Style queries had already shipped, and we could simply reference the same syntax for conditions (plus media() and supports() from Tab’s @when proposal) whereas in the 2018 proposal how conditions would work was largely undefined.

Lea Verou, “Inline conditionals in CSS?”

I like how Lea points out that CSS goes on to describe how CSS has always been a conditional language:

Folks… CSS had conditionals from the very beginning. Every selector is essentially a conditional!

Lea Verou, “Inline conditionals in CSS?”

True! The Cascade is the vehicle for evaluating selectors and matching them to HTML elements on a page. What if() brings to the table is a way to write inline conditions with selectors.

Syntax

It boils down to this:

<if()> = if( <container-query>, [<declaration-value>]{1, 2} )

…where:

  • Values can be nested to produce multiple branches.
  • If a third argument is not provided, it becomes equivalent to an empty token stream.

All of this is conceptual at the moment and nothing is set in stone. We’re likely to see things change as the CSSWG works on the feature. But as it currently stands, the idea seems to revolve around specifying a condition, and setting one of two declared styles — one as the “default” style, and one as the “updated” style when a match occurs.

.element {
  background-color:
    /* If the style declares the following custom property: */
    if(style(--variant: success),
      var(--color-green-50), /* Matched condition */
      var(--color-blue-50);  /* Default style */
    );
}

In this case, we’re looking for a style() condition where a CSS variable called --variant is declared and is set to a value of success, and:

  • …if --variant is set to success, we set the value of success to --color-green-50 which is a variable mapped to some greenish color value.
  • …if --variant is not set to success, we set the value of the success to --color-blue-50 which is a variable mapped to some bluish color value.

The default style would be optional, so I think it can be omitted in some cases for slightly better legibility:

.element {
  background-color:
    /* If the style declares the following custom property: */
    if(style(--variant: success),
      var(--color-green-50) /* Matched condition */
    );
}

The syntax definition up top mentions that we could support a third argument in addition to the matched condition and default style that allows us to nest conditions within conditions:

background-color: if(
  style(--variant: success), var(--color-success-60), 
    if(style(--variant: warning), var(--color-warning-60), 
      if(style(--variant: danger), var(--color-danger-60), 
        if(style(--variant: primary), var(--color-primary)
      )
    ),
  )
);

Oomph, looks like some wild inception is happening in there! Lea goes on to suggest a syntax that would result in a much flatter structure:

<if()> = if( 
  [ <container-query>, [<declaration-value>]{2}  ]#{0, },
  <container-query>, [<declaration-value>]{1, 2} 
)

In other words, nested conditions are much more flat as they can be declared outside of the initial condition. Same concept as before, but a different syntax:

background-color: if(
  style(--variant: success), var(--color-success-60), 
  style(--variant: warning), var(--color-warning-60),
  style(--variant: danger), var(--color-danger-60), 
  style(--variant: primary), var(--color-primary)
);

So, rather than one if() statement inside another if() statement, we can lump all of the possible matching conditions into a single statement.

We’re attempting to match an if() condition by querying an element’s styles. There is no corresponding size() function for querying dimensions — container queries implicitly assume size:

.element {
  background: var(--color-primary);

  /* Condition */
  @container parent (width >= 60ch) {
    /* Applied styles */
    background: var(--color-success-60);
  }
}

And container queries become style queries when we call the style() function instead:

.element {
  background: orangered;

  /* Condition */
  @container parent style(--variant: success) {
    /* Applied styles */
    background: dodgerblue;
  }
}

Style queries make a lot more sense to me when they’re viewed in the context of if(). Without if(), it’s easy to question the general usefulness of style queries. But in this light, it’s clear that style queries are part of a much bigger picture that goes beyond container queries alone.

There’s still plenty of things to suss out with the if() syntax. For example, Tab Atkins describes a possible scenario that could lead to confusion between what is the matched condition and default style parameters. So, who knows how this all shakes out in the end!

Conditions supporting other conditions

As we’ve already noted, if() is far from the only type of conditional check already provided in CSS. What would it look like to write an inline conditional statement that checks for other conditions, such as @supports and @media?

In code:

background-color: if(
  supports( /* etc. */ ),
  @media( /* etc. */ )
);

The challenge would be container supporting size queries. As mentioned earlier, there is no explicit size() function; instead it’s more like an anonymous function.

@andruud has a succinctly describes the challenge in the GitHub discussion:

I don’t see why we couldn’t do supports() and media(), but size queries would cause cycles with layout that are hard/impossible to even detect. (That’s why we needed the restrictions we currently have for size CQs in the first place.

“Can’t we already do this with [X] approach?”

When we were looking at the syntax earlier, you may have noticed that if() is just as much about custom properties as it is about conditionals. Several workarounds have emerged over the years to mimic what we’d gain if() we could set a custom property value conditionally, including:

  • Using custom properties as a Boolean to apply styles or not depending on whether it is equal to 0 or 1. (Ana has a wonderful article on this.)
  • Using a placeholder custom property with an empty value that’s set when another custom property is set, i.e. “the custom property toggle trick” as Chris describes it.
  • Container Style Queries! The problem (besides lack of implementation) is that containers only apply styles to their descendants, i.e., they cannot apply styles to themselves when they meet a certain condition, only its contents.

Lea gets deep into this in a separate post titled “Inline conditional statements in CSS, now?” that includes a table that outlines and compares approaches, which I’ll simply paste below. The explanations are full of complex CSS nerdery but are extremely helpful for understanding the need for if() and how it compares to the clever “hacks” we’ve used for years.

MethodInput valuesOutput valuesProsCons
Binary Linear InterpolationNumbersQuantitativeCan be used as part of a valueLimited output range
Togglesvar(--alias) (actual values are too weird to expose raw)AnyCan be used in part of a valueWeird values that need to be aliased
Paused animationsNumbersAnyNormal, decoupled declarationsTakes over animation property

Cascade weirdness
Type GrindingKeywordsAny value supported by the syntax descriptorHigh flexibility for exposed APIGood encapsulationMust insert CSS into light DOM

Tedious code (though can be automated with build tools)

No Firefox support (though that’s changing)
Variable animation nameKeywordsAnyNormal, decoupled declarationsImpractical outside of Shadow DOM due to name clashes

Takes over animation property

Cascade weirdness

Happy birthday, Lea!

Belated by two weeks, but thanks for sharing the spoils of your big day with us! 🎂

References

To Shared LinkPermalink on CSS-Tricks


“If” CSS Gets Inline Conditionals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/if-css-gets-inline-conditionals/feed/ 6 379002
Transitioning to Auto Height https://css-tricks.com/transitioning-to-auto-height/ https://css-tricks.com/transitioning-to-auto-height/#comments Fri, 28 Jun 2024 13:44:01 +0000 https://css-tricks.com/?p=378862 I know this is something Chris has wanted forever, so it’s no surprise he’s already got a fantastic write-up just a day after the news broke. In fact, I first learned about it from his post and was unable …


Transitioning to Auto Height originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I know this is something Chris has wanted forever, so it’s no surprise he’s already got a fantastic write-up just a day after the news broke. In fact, I first learned about it from his post and was unable to dredge up any sort of announcement. So, I thought I’d jot some notes down because it feels like a significant development.

The news: transitioning to auto is now a thing! Well, it’s going to be a thing. Chrome Canary recently shipped support for it and that’s the only place you’ll find it for now. And even then, we just don’t know if the Chrome Canary implementation will find its way to the syntax when the feature becomes official.

The problem

Here’s the situation. You have an element. You’ve marked it up, plopped in contents, and applied a bunch of styles to it. Do you know how tall it is? Of course not! Sure, we can ask JavaScript to evaluate the element for us, but as far as CSS is concerned, the element’s computed dimensions are unknown.

That makes it difficult to, say, animate that element from height: 0 to height: whatever. We need to know what “whatever” is and we can only do that by setting a fixed height on the element. That way, we have numbers to transition from zero height to that specific height.

.panel {
  height: 0;
  transition: height 0.25s ease-in;

  &.expanded {
    height: 300px;
  }
}

But what happens if that element changes over time? Maybe the font changes, we add padding, more content is inserted… anything that changes the dimensions. We likely need to update that height: 300px to whatever new fixed height works best. This is why we often see JavaScript used to toggle things that expand and contract in size, among other workarounds.

I say this is about the height property, but we’re also talking about the logical equivalent, block-size, as well as width and inline-size. Or any direction for that matter!

Transitioning to auto

That’s the goal, right? We tend to reach for height: auto when the height dimension is unknown. From there, we let JavaScript calculate what that evaluates to and take things from there.

The current Chrome implementation uses CSS calc-size() to do the heavy lifting. It recognizes the auto keyword and, true to its name, calculates that number. In other words, we can do this instead of the fixed-height approach:

.panel {
  height: 0;
  transition: height 0.25s ease-in;

  &.expanded {
    height: calc-size(auto);
  }
}

That’s really it! Of course, calc-size() is capable of more complex expressions but the fact that we can supply it with just a vague keyword about an element’s height is darn impressive. It’s what allows us to go from a fixed value to the element’s intrinsic size and back.

I had to give it a try. I’m sure there are a ton of use cases here, but I went with a floating button in a calendar component that indicates a certain number of pending calendar invites. Click the button, and a panel expands above the calendar and reveals the invites. Click it again and the panel goes back to where it came from. JavaScript is handling the click interaction, triggering a class change that transitions the height in CSS.

A video in case you don’t feel like opening Canary:

This is the relevant CSS:

.invite-panel {
  height: 0;
  overflow-y: clip;
  transition: height 0.25s ease-in;
}

On click, JavaScript sets auto height on the element as an inline style to override the CSS:

<div class="invite-panel" style="height: calc(auto)">

The transition property in CSS lets the browser know that we plan on changing the height property at some point, and to make it smooth. And, as with any transition or animation, it’s a good idea to account for motion sensitivities by slowing down or removing the motion with prefers-reduced-motion.

What about display: none?

This is one of the first questions that popped into my head when I read Chris’s post and he gets into that as well. Transitioning from an element from display: none to its intrinsic size is sort of like going from height: 0. It might seem like a non-displayed element has zero height, but it actually does have a computed height of auto unless a specific height is declared on it.

DevTools showing computed values for an element with display none. The height value shows as auto.

So, there’s extra work to do if we want to transition from display: none in CSS. I’ll simply plop in the code Chris shared because it nicely demonstrates the key parts:

.element {
  /* hard mode!! */
  display: none;

  transition: height 0.2s ease-in-out;
  transition-behavior: allow-discrete;

  height: 0; 
  @starting-style {
    height: 0;
  }

  &.open {
    height: calc(auto);
  }
}
  • The element starts with both display: none and height: 0.
  • There’s an .open class that sets the element’s height to calc-size(auto).

Those are the two dots we need to connect and we do it by first setting transition-behavior: allow-discrete on the element. This is new to me, but the spec says that transition-behavior “specifies whether transitions will be started or not for discrete properties.” And when we declare allow-discrete, “transitions will be started for discrete properties as well as interpolable properties.”

Well, DevTools showed us right there that height: auto is a discrete property! Notice the @starting-style declaration, though. If you’re unfamiliar with it, you’re not alone. The idea is that it lets us set a style for a transition to “start” with. And since our element’s discrete height is auto, we need to tell the transition to start at height: 0 instead:

.element {
  /* etc. */

  @starting-style {
    height: 0;
  }
}

Now, we can move from zero to auto since we’re sorta overriding the discrete height with @starting-style. Pretty cool we can do that!

To Shared LinkPermalink on CSS-Tricks


Transitioning to Auto Height originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/transitioning-to-auto-height/feed/ 7 378862
Poppin’ In https://css-tricks.com/poppin-in/ https://css-tricks.com/poppin-in/#comments Wed, 26 Jun 2024 16:37:05 +0000 https://css-tricks.com/?p=378637 Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello while we get to know the Popover API a bit.


Poppin’ In originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello. 👋

Speaking of “popping” in, I’ve been playing with the Popover API a bit. We actually first noted it wayyyyy back in 2018 when Chris linked up some information about the <dialog> element. But it’s only been since April of this year that we finally have full Popover API support in modern browsers.

There was once upon a time that we were going to get a brand-new <popover> element in HTML for this. Chromium was working on development as recently as September 2021 but reached a point where it was dropped in favor of a popover attribute instead. That seems to make the most sense given that any element can be a popover — we merely need to attach it to the attribute to enable it.

<div popover>
  <!-- Stuff -->
</div>

This is interesting because let’s say we have some simple little element we’re using as a popover:

<div>👋</div>

If this is all the markup we have and we do absolutely nothing in the CSS, then the waving emoji displays as you might expect.

Add that popover attribute to the mix, however, and it’s gone!

That’s perhaps the first thing that threw me off. Most times something disappears and I assume I did something wrong. But cracking open DevTools shows this is exactly what’s supposed to happen.

DevTools inspector showing the computed values for an element with the popover attribute.
The element is set to display: none by default.

There may be multiple popovers on a page and we can differentiate them with IDs.

<div popover id="tooltip">
  <!-- Stuff -->
</div>

<div popover id="notification">
  <!-- Stuff -->
</div>

That’s not enough, as we also need some sort of “trigger” to make the popover, well, pop! We get another attribute that turns any button (or <input>-flavored button) into that trigger.

<button popovertarget="wave">Say Hello!</button>
<div popover id="wave">👋</div>

Now we have a popover “targeted ” to a <button>. When the button is clicked, the popover element toggles visibility.

This is where stuff gets really fun because now that CSS is capable of handling logic to toggle visibility, we can focus more on what happens when the click happens.

Like, right now, the emoji is framed by a really thick black border when it is toggled on. That’s a default style.

Notice that the border sizing in the Box Model diagram.

A few other noteworthy things are going on in DevTools there besides the applied border. For example, notice that the computed width and height behave more like an inline element than a block element, even though we are working with a straight-up <div> — and that’s true even though the element is clearly computing as display: block. Instead, what we have is an element that’s sized according to its contents and it’s placed in the dead center of the page. We haven’t even added a single line of CSS yet!

Speaking of CSS, let’s go back to removing that default border. You might think it’s possible by declaring no border on the element.

/* Nope 👎 */
#wave {
  border: 0;
}

There’s actually a :popover-open pseudo-class that selects the element specifically when it is in an “open” state. I’d love this to be called :popover-popped but I digress. The important thing is that :popover-open only matches the popover element when it is open, meaning these styles are applied after those declared on the element selector, thus overriding them.

Another way to do this? Select the [popover] attribute:

/* Select all popovers on the page */
[popover] {
  border: 0;
}

/* Select a specific popover: */
#wave[popover] {
  border: 0;
}

/* Same as: */
#wave:popover-open {
  border: 0;
}

With this in mind, we can, say, attach an animation to the #wave in its open state. I’m totally taking this idea from one of Jhey’s demos.

Wait, wait, there’s more! Popovers can be a lot like a <dialog> with a ::backdrop if we need it. The ::backdrop pseudo-element can give the popover a little more attention by setting it against a special background or obscuring the elements behind it.

I love this example that Mojtaba put together for us in the Almanac, so let’s go with that.

Can you imagine all the possibilities?! Like, how much easier will it be to create tooltips now that CSS has abstracted the visibility logic? Much, much easier.

Michelle Barker notes that this is probably less of a traditional “tooltip” that toggles visibility on hover than it is a “toggletip” controlled by click. That makes a lot of sense. But the real reason I mention Michelle’s post is that she demonstrates how nicely the Popover API ought to work with CSS Anchor Positioning as it gains wider browser support. That will help clean out the magic numbers for positioning that are littering my demo.

Here’s another gem from Jhey: a popover doesn’t have to be a popover. Why not repurpose the Popover API for other UI elements that rely on toggled visibility, like a slide-out menu?

Oh gosh, look at that: it’s getting late. There’s a lot more to the Popover API that I’m still wrapping my head around, but even the little bit I’ve played with feels like it will go a long way. I’ll drop in a list of things I have bookmarked to come back to. For now, though, thanks for letting me pop back in for a moment to say hi.


Poppin’ In originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/poppin-in/feed/ 2 378637
https://css-tricks.com/378960-2/ https://css-tricks.com/378960-2/#respond Mon, 24 Jun 2024 18:28:26 +0000 https://css-tricks.com/?p=378960 CSS Meditation #8: .work + .life { border: 10px solid #000; }


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #8: .work + .life { border: 10px solid #000; }


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378960-2/feed/ 0 378960
https://css-tricks.com/378958-2/ https://css-tricks.com/378958-2/#respond Mon, 24 Jun 2024 18:27:59 +0000 https://css-tricks.com/?p=378958 CSS Meditation #7: Nobody is perf-ect.…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #7: Nobody is perf-ect.


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378958-2/feed/ 0 378958
https://css-tricks.com/378956-2/ https://css-tricks.com/378956-2/#respond Mon, 24 Jun 2024 18:27:28 +0000 https://css-tricks.com/?p=378956 CSS Meditation #6: The color space is always calc(rgb(0 255 0)+er) on the other side of the fence.…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #6: The color space is always calc(rgb(0 255 0)+er) on the other side of the fence.


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378956-2/feed/ 0 378956
https://css-tricks.com/378954-2/ https://css-tricks.com/378954-2/#respond Mon, 24 Jun 2024 18:26:50 +0000 https://css-tricks.com/?p=378954 CSS Meditation #5: :where(:is(.my-mind))


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #5: :where(:is(.my-mind))


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378954-2/feed/ 0 378954
https://css-tricks.com/378952-2/ https://css-tricks.com/378952-2/#respond Mon, 24 Jun 2024 18:26:12 +0000 https://css-tricks.com/?p=378952 CSS Meditation #4: Select, style, adjust. Select, style, adjust. Select, sty……


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #4: Select, style, adjust. Select, style, adjust. Select, sty…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378952-2/feed/ 0 378952
https://css-tricks.com/378950-2/ https://css-tricks.com/378950-2/#respond Mon, 24 Jun 2024 18:25:41 +0000 https://css-tricks.com/?p=378950 CSS Meditation #3: A pseudo is as a pseudo does.…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #3: A pseudo is as a pseudo does.


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378950-2/feed/ 0 378950
https://css-tricks.com/378948-2/ https://css-tricks.com/378948-2/#comments Mon, 24 Jun 2024 18:25:14 +0000 https://css-tricks.com/?p=378948 CSS Meditation #2: Who gives a flying frick what constitutes a “programming” language.…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #2: Who gives a flying frick what constitutes a “programming” language.


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378948-2/feed/ 1 378948
https://css-tricks.com/378946-2/ https://css-tricks.com/378946-2/#respond Mon, 24 Jun 2024 18:22:35 +0000 https://css-tricks.com/?p=378946 CSS Meditation #1: If the code works as expected and it fits your mental model, then it’s perfect.…


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Meditation #1: If the code works as expected and it fits your mental model, then it’s perfect.


originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/378946-2/feed/ 0 378946
CSS Container Queries https://css-tricks.com/css-container-queries/ https://css-tricks.com/css-container-queries/#comments Mon, 10 Jun 2024 16:12:50 +0000 https://css-tricks.com/?p=378111 The main idea of CSS Container Queries is to register an element as a “container” and apply styles to other elements when the container element meets certain conditions.


CSS Container Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>

Container queries are often considered a modern approach to responsive web design where traditional media queries have long been the gold standard — the reason being that we can create layouts made with elements that respond to, say, the width of their containers rather than the width of the viewport.

.parent {
  container-name: hero-banner;
  container-type: inline-size;

  /* or container: hero-banner / inline-size; */
}

}

.child {
  display: flex;
  flex-direction: column;
}

/* When the container is greater than 60 characters... */
@container hero-banner (width > 60ch) {
  /* Change the flex direction of the .child element. */
  .child { 
    flex-direction: row;
  }
}

Why care about CSS Container Queries?

  1. When using a container query, we give elements the ability to change based on their container’s size, not the viewport.
  1. They allow us to define all of the styles for a particular element in a more predictable way.
  1. They are more reusable than media queries in that they behave the same no matter where they are used. So, if you were to create a component that includes a container query, you could easily drop it into another project and it will still behave in the same predictable fashion.
  1. They introduce new types of CSS length units that can be used to size elements by their container’s size.

Registering Elements as Containers

.cards {
  container-name: card-grid;
  container-type: inline-size;

  /* Shorthand */
  container: card-grid / inline-size;
}

This example registers a new container named card-grid that can be queried by its inline-size, which is a fancy way of saying its “width” when we’re working in a horizontal writing mode. It’s a logical property. Otherwise, “inline” would refer to the container’s “height” in a vertical writing mode.

  • The container-name property is used to register an element as a container that applies styles to other elements based on the container’s size and styles.
  • The container-type property is used to register an element as a container that can apply styles to other elements when it meets certain conditions.
  • The container property is a shorthand that combines the container-name and container-type properties into a single declaration.

Some Possible Gotchas

Querying a Container

@container my-container (width > 60ch) {
  article {
    flex-direction: row;
  }
}
  • The @container at-rule property informs the browser that we are working with a container query rather than, say, a media query (i.e., @media).
  • The my-container part in there refers to the container’s name, as declared in the container’s container-name property.
  • The article element represents an item in the container, whether it’s a direct child of the container or a further ancestor. Either way, the element must be in the container and it will get styles applied to it when the queried condition is matched.

Some Possible Gotchas

Container Queries Properties & Values

Container Queries Properties & Values

container-name

container-name: none | <custom-ident>+;
Value Descriptions
  • none: The element does not have a container name. This is true by default, so you will likely never use this value, as its purpose is purely to set the property’s default behavior.
  • <custom-ident>: This is the name of the container, which can be anything, except for words that are reserved for other functions, including defaultnoneatno, and or. Note that the names are not wrapped in quotes.
  • Initial value: none
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: none or an ordered list of identifiers
  • Canonical order: Per grammar
  • Animation: Not animatable

container-type

container-type: normal | size | inline-size;
Value Descriptions
  • normal: This indicates that the element is a container that can be queried by its styles rather than size. All elements are technically containers by default, so we don’t even need to explicitly assign a container-type to define a style container.
  • size: This is if we want to query a container by its size, whether we’re talking about the inline or block direction.
  • inline-size: This allows us to query a container by its inline size, which is equivalent to width in a standard horizontal writing mode. This is perhaps the most commonly used value, as we can establish responsive designs based on element size rather than the size of the viewport as we would normally do with media queries.
  • Initial value: normal
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: As specified by keyword
  • Canonical order: Per grammar
  • Animation: Not animatable

container

container: <'container-name'> [ / <'container-type'> ]?
Value Definitons

If <'container-type'> is omitted, it is reset to its initial value of normalwhich defines a style container instead of a size container. In other words, all elements are style containers by default, unless we explicitly set the container-type property value to either size or inline-size which allows us to query a container’s size dimensions.

  • Initial value: none / normal
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: As specified
  • Canonical order: Per grammar
  • Animation: Not animatable

Container Length Units

Container Width & Height Units

UnitNameEquivalent to…
cqwContainer query width1% of the queried container’s width
cqhContainer query height1% of the queried container’s height

Container Logical Directions

UnitNameEquivalent to…
cqiContainer query inline size1% of the queried container’s inline size, which is its width in a horizontal writing mode.
cqbContainer query block size1% of the queried container’s inline size, which is its height in a horizontal writing mode.

Container Minimum & Maximum Lengths

UnitNameEquivalent to…
cqminContainer query minimum sizeThe value of cqi or cqb, whichever is smaller.
cqmaxContainer query maximum sizeThe value of cqi or cqb, whichever is larger.

Container Style Queries

Container Style Queries is another piece of the CSS Container Queries puzzle. Instead of querying a container by its size or inline-size, we can query a container’s CSS styles. And when the container’s styles meet the queried condition, we can apply styles to other elements. This is the sort of “conditional” styling we’ve wanted on the web for a long time: If these styles match over here, then apply these other styles over there.

CSS Container Style Queries are only available as an experimental feature in modern web browsers at the time of this writing, and even then, style queries are only capable of evaluating CSS custom properties (i.e., variables).

Browser Support

The feature is still considered experimental at the time of this writing and is not supported by any browser, unless enabled through feature flags.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
129NoNo126TP

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
126No12618.0

Registering a Style Container

article {
  container-name: card;
}

That’s really it! Actually, we don’t even need the container-name property unless we need to target it specifically. Otherwise, we can skip registering a container altogether.

And if you’re wondering why there’s no container-type declaration, that’s because all elements are already considered containers. It’s a lot like how all elements are position: relative by default; there’s no need to declare it. The only reason we would declare a container-type is if we want a CSS Container Size Query instead of a CSS Container Style Query.

So, really, there is no need to register a container style query because all elements are already style containers right out of the box! The only reason we’d declare container-name, then, is simply to help select a specific container by name when writing a style query.

Using a Style Container Query

@container style(--bg-color: #000) {
  p { color: #fff; }
}

In this example, we’re querying any matching container (because all elements are style containers by default).

Notice how the syntax it’s a lot like a traditional media query? The biggest difference is that we are writing @container instead of @media. The other difference is that we’re calling a style() function that holds the matching style condition. This way, a style query is differentiated from a size query, although there is no corresponding size() function.

In this instance, we’re checking if a certain custom property named --bg-color is set to black (#000). If the variable’s value matches that condition, then we’re setting paragraph (p) text color to white (#fff).

Custom Properties & Variables

.card-wrapper {
  --bg-color: #000;
}
.card {
  @container style(--bg-color: #000) {
    /* Custom CSS */
  }
}

Nesting Style Queries

@container style(--featured: true) {
  article {
    grid-column: 1 / -1;
  }
  @container style(--theme: dark) {
    article {
      --bg-color: #000;
      --text: #fff;
    }
  }
}

Specification

CSS Container Queries are defined in the CSS Containment Module Level 3 specification, which is currently in Editor’s Draft status at the time of this writing.

Browser Support

Browser support for CSS Container Size Queries is great. It’s just style queries that are lacking support at the time of this writing.

  • Chrome 105 shipped on August 30, 2022, with support.
  • Safari 16 shipped on September 12, 2022, with support.
  • Firefox 110 shipped on February 14, 2023, with support.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
106110No10616.0

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12612712616.0

Demos!

Many, many examples on the web demonstrate how container queries work. The following examples are not unique in that regard in that they illustrate the general concept of applying styles when a container element meets a certain condition.

You will find plenty more examples listed in the References at the end of this guide, but check out Ahmad Shadeed’s Container Queries Lab for the most complete set of examples because it also serves as a collection of clever container query use cases.

Card Component

In this example, a “card” component changes its layout based on the amount of available space in its container.

Call to Action Panel

This example is a lot like those little panels for signing up for an email newsletter. Notice how the layout changes three times according to how much available space is in the container. This is what makes CSS Container Queries so powerful: you can quite literally drop this panel into any project and the layout will respond as it should, as it’s based on the space it is in rather than the size of the browser’s viewport.

Stepper Component

This component displays a series of “steps” much like a timeline. In wider containers, the stepper displays steps horizontally. But if the container becomes small enough, the stepper shifts things around so that the steps are vertically stacked.

Icon Button

Sometimes we like to decorate buttons with an icon to accentuate the button’s label with a little more meaning and context. And sometimes we don’t know just how wide that button will be in any given context, which makes it tough to know when exactly to hide the icon or re-arrange the button’s styles when space becomes limited. In this example, an icon is displayed to the right edge of the button as long as there’s room to fit it beside the button label. If room runs out, the button becomes a square tile that stacks the icons above the label. Notice how the border-radius is set in container query units, 4cqi, which is equal to 4% of the container’s inline-size (i.e. width) and results in rounder edges as the button grows in size.

Pagination

Pagination is a great example of a component that benefits from CSS Container Queries because, depending on the amount of space we have, we can choose to display links to individual pages, or hide them in favor of only two buttons, one to paginate to older content and one to paginate to newer content.

Articles & Tutorials

General Information

Container Size Query Tutorials

Container Style Queries

References


CSS Container Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-container-queries/feed/ 2 378111
CSS Length Units https://css-tricks.com/css-length-units/ https://css-tricks.com/css-length-units/#comments Mon, 03 Jun 2024 19:22:02 +0000 https://css-tricks.com/?p=375815 A comprehensive guide covering nine types of lengths that CSS uses to size elements in terms of dimensions, space, time, and even sound.


CSS Length Units originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>

Overview

Many CSS properties accept numbers as values. Sometimes those are whole numbers. Sometimes they’re decimals and fractions. Other times, they’re percentages. Whatever they are, the unit that follows a number determines the number’s computed length. And by “length” we mean any sort of distance that can be described as a number, such as the physical dimensions of an element, a measure of time, geometric angles… all kinds of things!

At the time of this writing, the CSS Values and Units Module Level 4 specification defines a bunch of different CSS units — and many of those are relatively new (this pun will make sense later).

Quick Reference

Absolute units
UnitName
cmCentimeters
mmMillimeters
QQuarter-millimeters
inInches
pcPicas
ptPoints
pxPixels
Font units
UnitRelative to…
emThe font size of the element, or its nearest parent container
exThe 0-height of the element’s font 
capThe cap height (the nominal height of capital letters) of the element’s font 
chThe width of the 0 character of the font in use
icThe average width of a full glyph of the font in use, as represented by the “水” (U+6C34) glyph
remThe font-size value that’s set on the root (html) element
lhThe line-height value that’s set on the element 
rlhThe line-height that’s set on the root (html) element 
vw1% of viewport’s width
vh1% of viewport’s height
vi1% of viewport’s size in the root element’s inline axis
vb1% of viewport’s size in the root element’s block axis
vminEqual to 1% of the vw or vh, whichever is smaller
vmaxEqual to 1% of the vw or vh, whichever is larger
Viewport units
vw1% of viewport’s width
vh1% of viewport’s height
vi1% of viewport’s size in the root element’s inline axis
vb1% of viewport’s size in the root element’s block axis
vmin1% of the vw or vh, whichever is smaller
vmax1% of the vw or vh, whichever is larger
Container units
UnitRelative to
cqw1% of a query container’s width
cqh1% of a query container’s height
cqi1% of a query container’s inline size
cqb1% of a query container’s block size
cqminThe smaller value of cqi or cqb
cqmaxThe larger value of cqi or cqb
Angle units
UnitDescription
degThere are 360 degrees in a full circle.
gradThere are 400 gradians in a full circle.
radThere are 2π radians in a full circle.
turnThere is 1 turn in a full circle.
Time units
UnitDescription
sThere are 60 seconds in a minute, but there is no unit for minutes.
msThere are 1,000 milliseconds in a second.
Fractional units
UnitDescription
frOne fraction of the free space in a grid container.
Resolution units
UnitDescription
dpiDots per inch
dpcmDots per centimeter
dppxxDots per pixel unit
Frequency units
UnitDescription
HzRepresents the number of occurrences per second
kHzOne kiloHertz is equal to 1000 Hertz.

Introduction

You’re going to see a lot numbers in CSS. Here are a few examples?

/* Integers */
1

/* Pixels */
14px

/* em */
1.5em

/* rem */
3rem

/* Percentage */
50%

/* Characters */
650ch

/* Viewport units */
100vw
80vh
50dvh

/* Container units */
100cqi
50cqb

While these all mean different things, they essentially do the same thing: define an element’s dimensions in CSS. We need units in CSS because they determine how to size elements on a page, whether it’s the height of a box, the width of an image, the font-size of a heading, the margin between two elements, how long an animation runs, etc. Without them, the browser would have no way of knowing how to apply numbers to an element.

So, what the heck is px? What’s up with this thing called rem? How are these different than other length units? The unit defines what type of number we’re dealing with, and each one does something different, giving us lots of ways to size things in CSS.


Types of numbers

You may think a number is just a number, and you’re not wrong. Numbers are numbers! But we can distinguish between a few different types of numbers, which is helpful context for discussing the different types of units we attach them to since “number” can mean, well, a number of different things.

  • Integers (literally a unit-less number, e.g. 3)
  • Numbers (same as an integer, only measured in decimals, e.g. 3.2)
  • Dimensions (either a number or integer with a unit, e.g. 3.2rem)
  • Ratios (the quotient between two divided numbers, e.g. 3/2)
  • Percentages (e.g. 3%)

Got that? They’re all numbers but with nuances that make them ever-so-slightly different.

From here, we can think of numbers in CSS as falling into two specific types of unitsabsolute and relative. Let’s start things off our deep dive on CSS length units by breaking those down.


Absolute units

An absolute unit is like Bill Murray in the movie Groundhog Day: it’s always the same. In other words, whatever the number is, that’s exactly how it computes in the browser regardless of how other elements are sized.

The most common absolute value you’ll see is the pixel value. It’s sort of hard to define, but a pixel is the smallest building block of a graphical display, like a computer screen. And it’s based on the resolution of the screen. So, if you’re on a super high-resolution screen, a pixel will be smaller than it would be on a low-resolution screen, as the resolution can pack more pixels into a smaller amount of space for higher clarity. But look at the example below. All of the boxes are sized with pixels, so you can get a sense of how large 50px is compared to 250px.

Absolute values are nice in that they are predictable. That could, however, change in some situations, particularly when it comes to zooming. If, say, a user zooms into a page using browser settings, then anything defined with an absolute value is going to increase its absolute size accordingly. So, if the font-size of a paragraph is set to 20px, the paragraph is going to be larger as the user zooms closer into the page. And because zooming is often used to make content more readable, using absolute values that retain their sizing could be a good approach for making pages more accessible by allowing users to zoom things up to a spot that more comfortable to read.

But then again, see Josh Collinsworth’s click-baity, but fantastic, post titled “Why you should never use px to set font-size in CSS” for an exhaustive explanation of how pixels behave when used to set the font-size of an element. It’s a great read to better understand the behavior and limitations of pixel units.

And, hey: pixels are only one of many types of absolute lengths that are available in CSS. In fact, we can group them by the types of things they measure:

Length units

Length units are a little funny because they can technically be either an absolute unit or a relative unit. But we’re discussing them in absolute terms at the moment and will revisit them when we get further along to relative length units.

A length is essentially a dimension, which is any integer proceeded by a unit, according to the list of types of numbers we looked at earlier. And when we talk about dimensions, we’re really talking about the physical size of an element.

UnitName
cmCentimeters
mmMillimeters
QQuarter-millimeters
inInches
pcPicas
ptPoints
pxPixels

What we’re looking at here are the types of units you might use see on a tape measure (e.g., cm and in) or in print design (e.g. pc and pt). They are what they are and what you see is what you get.

Angle units

Angle units are purely geometric. They’re good for setting shape dimensions — like a circle’s radius, setting the direction of a linear-gradient(), or setting the how much we want to rotate() something.

UnitNameDescriptionExample
degDegreesA full circle is equal to 360deg.rotate(180deg)
gradGradiensA full circle is equal to 400grad.rotate(200grad)
radRadiensA full circle is equal to (i.e., 2 × 3.14), or about 6.2832rad.rotate(3.14rad)
turnTurnsA full circle is 1turn, like a bicycle wheel making one full rotation.rotate(.5turn)
Time units

Time units are what you’d expect to find on a clock or watch, but only measure in seconds and milliseconds. Apparently the web cannot be measured in minutes, hours, days, weeks, months, or years. Perhaps we’ll get a new category of “calendar units” at some point, or maybe there’s no good use case for that sort of thing. 🤷‍♂️

UnitNameDescriptionExample
sSecondsOne full minute of time is equal to 60s.animation-duration: 2s
msMillisecondsOne full second of time os equal to 1000ms.animation-duration: 2000ms
Frequency units

You won’t see frequency units used very often and for good reason: they’re not supported by any browser at the time of this writing. But they’re specced to change sound frequency, such as a sound’s pitch. The best I can make of it as it currently stands is that frequencies can be used to manipulate an audio file with a higher or lower pitch measured in hertz and kilohertz.

UnitNameDescriptionExample
HzHertzMeasures the number of frequencies per second<source src="tubthumping.mp3" type="audio/mpeg" frequency="100Hz">
kHzKilohertzA value of 1Hz is equal to 0.001kHz.<source src="tubthumping.mp3" type="audio/mpeg" frequency="0.1kHz">

If you’re wondering what constitutes a “low” pitch from a “high” one, the spec explains it like this:

[W]hen representing sound pitches, 200Hz (or 200hz) is a bass sound, and 6kHz (or 6khz) is a treble sound.

Resolution units

Resolution is how many little dots are packed into a screen — such as the screen you’re looking at right now — where more dots per inch of space improves the clarity and quality of the display. The fewer dots there are, the more pixelated and blurry the display.

Why would you need something like this? Well, it’s great for targeting styles to specific screens that support certain resolutions in a media query.

img {
  max-width: 500px;
}

/* Double the resolution and above */
@media (min-resolution >= 2dppx) {
  img {
    max-width: 100%;
  }
}
UnitNameDescriptionExample
dpiDots per inchThe number of dots packed into one inch of space.@media
(min-resolution: 96dpi) {}
dpcmDots per centimeterThe number of dots packed into one centimeter of space.@media
(min-resolution: 960dpcm) {}
dppx (or x)Dots per pixelThe number of dots packed into one pixel of space.@media
(min-resolution: 1dppx) {}

Interestingly, the specification makes mention of an infinite value that is supported by resolution media queries for targeting screens without resolution constraints. It’s not so much of a “catch-all” value for targeting any sort of screen, but for cases when we’re using the media query range syntax to evaluate whether a certain value is greater than, less than, or equal to it:

For output mediums that have no physical constraints on resolution (such as outputting to vector graphics), this feature must match the infinite value. For the purpose of evaluating this media feature in the range contextinfinite must be treated as larger than any possible <resolution>. (That is, a query like (resolution > 1000dpi)will be true for an infinite media.)

W3C Media Queries Level 4 specification

Relative units

relative unit is extremely well-named because whatever value we use for a relative unit depends on the size of something else. Say we have an HTML element, a <div>, and we give it an absolute height value (not a relative one) of 200px.

<div class="box">
  I am 200 pixels tall
</div>
.box {
  height: 200px;
}

That height will never change. The .box element will be 200px tall no matter what. But let’s say we give the element a relative width (not an absolute one) of 50%.

<div class="box">
  I am 200 pixels tall and 50% wide
</div>
.box {
  height: 200px;
  width: 50%;
}

What happens to our box? It takes up 50%, or half, of the available space on the screen.

See that? Go ahead and open that demo in a new window and change the width of the screen. And notice, too, how the height never changes because it’s an absolute length unit in pixels. The width, meanwhile, is fluidly resized as “50% of the available space” changes with the width of the screen.

That’s what we mean when talking about computed values with relative numbers. A relative number acts sort of like a multiplier that calculates the value used to set a length based on what type of unit it is relative to. So, a value of 3rem is going to wind up becoming a different value when it is computed.

Percentages, like 50%, are only one kind of relative unit. We have many, many others. Once again, it’s helpful to break things out into separate groups to understand the differences just as we did earlier with absolute units.

Percentages

We’ve already discussed percentages in pretty good detail. What makes a percentage relative is that it computes to a number value based on the length of another element. So, an element that is given width: 25% in a container that is set to width: 1000px computes to width: 250px.

UnitNameRelative to…
%PercentThe size of the element’s parent container.
Font relative units

The em and rem units we looked at earlier are prime examples of relative units that you will see all over the place. They’re incredibly handy, as we saw, because changing the font-size value of an element’s parent or the <html> element, respectively, causes the element’s own font-size value to update in accordance with the updated value.

In other words, we do not need to directly change an element’s font-size when updating the font-size of other elements — it’s relative and scales with the change.

UnitNameRelative to…
emElementThe font-size value of the element’s parent container.
remRoot elementThe font-size value of the <html> element.
chCharacterThe width of the 0 character relative to the parent element’s font. The computed width may update when replacing one font with another, except for monospace fonts that are consistently sized.
rchRoot characterThe same thing as a ch unit except it is relative to the font of the root element, i.e. <html>.
lhLine heightThe line-height value of the element’s parent container.
rlhRoot element line heightThe line-height value of the <html> element.
capCapital letterThe height of a capital letter for a particular font relative to the parent element.
rcapRoot capital letterThe same measure as cap but relative to the root element, i.e. <html>.
icInternational characterThe width of a CJK character, or foreign glyph, e.g. from a Chinese font, relative to an element’s parent container.
ricRoot international characterThe same measure as ic but relative to the root element, i.e. <html>.
exX-heightThe height of the letter x of a particular font, or an equivalent for fonts that do not contain an x character, relative to the parent element.
rexRoot x-heightThe same measure as ex but relative to the root element, i.e. <html>.

Some of those terms will make more sense to typography nerds than others. The following diagram highlights the lines that correspond to relative font units.

Four lines drawn over a line of text illustrating the ascender height, x-height, baseline, and descender height.

So, at the expense of beating this concept into the ground, if width: 10ch computes to a certain number of pixels when using one font, it’s likely that the computed value will change if the font is swapped out with another one with either larger or smaller characters.

Viewport relative units
UnitNameRelative to…
vh / vwViewport Height / Viewport WidthThe height and width of the viewport (i.e., visible part of the screen), respectively.
vmin / vmaxViewport Minimum / Viewport MaximumThe lesser and greater of vh and vw, respectively.
lvh / lvwLarge Viewport Height / Large Viewport WidthThe height and width of the viewport when the device’s virtual keyboard or browser UI is out of view, leaving a larger amount of available space.
lvb / lviLarge Viewport Block / Large Viewport InlineThese are the logical equivalents of lvh and lvw, indicating the block and inline directions.
svh / svwSmall Viewport Height / Small Viewport WidthThe height and width of the viewport when the device’s virtual keyboard or browser UI is in view, making the amount of available space smaller.
svb / sviSmall Viewport Block / Small Viewport InlineThese are the logical equivalents of svh and svw, indicating the block and inline directions.
dvh / dvwDynamic Viewport Height / Dynamic Viewport WidthThe height and width of the viewport accounting for the available space changing if, say, the device’s virtual keyboard or browser UI is in view.
dvb / dviDynamic Viewport Block / Dynamic Viewport InlineThese are the logical equivalents of dvh and dvw, indicating the block and inline directions.
dvmin / dvmaxDynamic Viewport Minimum / Dynamic Viewport MaximumThe lesser and greater of dvh/dvb and dvw/dvi, respectively.

Ah, viewport units! When we say that something should be 100% wide, that means it takes up the full width of the contain it is in. That’s because a percentage unit is always relative to its nearest parent element. But a  viewport unit is always relative to the size of the viewport, or browser window. If an element has a viewport height of 100vh and a viewport width of 100vw, then it will be as tall and wide as the full browser window.

This can be a neat way to create something like a hero banner at the top of your website. For example, we can make a banner that is always one half (50vh) the height of the viewport making it prominent no matter how tall someone’s browser is. Change the CSS in the top-left corner of the following demo from height: 50vh to something else, like 75vh to see how the banner’s height responds.

There’s something else that’s very important to know when working with viewport units. You know how mobile phones, like iPhone or an Android device, have virtual keyboards where you type directly on the screen? That keyboard changes the size of the viewport. That means that whenever the keyboard opens, 100vh is no longer the full height of the screen but whatever space is leftover while the keyboard is open, and the layout could get super squished as a result.

That’s why we have the svh, lvh, and dvh units in addition to vh:

  • svh is equal to the “small” viewport height, which occurs when the keyboard is open.
  • lvh is equal to the “large” viewport height, which is when the keyboard is disabled and out of view.
  • dvh is a happy medium between svh and lvh in that it is “dynamic” and updates its value accordingly when the keyboard is displayed or not.
  • dvmin / dvmax is the greater ore lesser of dvh, respectively.

It’s a bit of a tightrope walk in some cases and a good reason why container queries and their units (which we’ll get to next) are becoming more popular. Check out Ahmed Shader’s article “New Viewport Units” for a comprehensive explanation about viewport units with detailed examples of the issues you may run into. You may also be interested in Sime Vidas’s “New CSS Viewport Units Do Not Solve The Classic Scrollbar Problem” for a better understanding of how viewport units compute values.

Container relative units
UnitNameEquivalent to…
cqwContainer query width1% of the queried container’s width
cqhContainer query height1% of the queried container’s height
cqiContainer query inline size1% of the queried container’s inline size, which is its width in a horizontal writing mode.
cqbContainer query block size1% of the queried container’s inline size, which is its height in a horizontal writing mode.
cqminContainer query minimum sizeThe value of cqi or cqb, whichever is smaller.
cqmaxContainer query maximum sizeThe value of cqi or cqb, whichever is larger.

Container units are designed to be used with container queries. Just as we are able to target a specific screen size with a media query, we can target the specific size of a particular element and apply styles using a container query.

We won’t do a big ol’ deep dive into container queries here. The relevant bit is that we have CSS length units that are relative to a container’s size. For example, if we were to define a container:

.parent-container {
  container-type: inline-size;
}

…then we’re watching that element’s inline-size — which is equivalent to width in a horizontal writing mode — and can apply styles to other elements when it reaches certain sizes.

.child-element {
  background: rebeccapurple;
  width: 100%;

  @container parent (width > 30ch) {
    .child-element {
      background: dodgeblue;
      width: 50cqi;
    }
  }
}

Try resizing the following demo. When the parent container is greater than 30ch, the child element will change backgrounds and shrink to one-half the parent container’s width, or 50cqi.


What about unit-less numbers?

Oh yeah, there are times when you’re going to see numbers in CSS that don’t have any unit at all — just a single integer or number without anything appended to it.

aspect-ratio: 2 / 1; /* Ratio */
column-count: 3; /* Specifies a number of columns */
flex-grow: 1; /* Allows the element to stretch in a flex layout */
grid-column-start: 4; /* Places the element on a specific grid line */
order: 2; /* Sets the order of elemnents in a flex or grid layout */
scale: 2; /* The elementis scaled up or down by a factor */
z-index: 100; /* Element is placed in a numbered layer for stacking */
zoom: 0.2;  /* The element zooms in or out by a factor */

This isn’t a comprehensive list of all the CSS properties that accept unit-less numeric values, but it is a solid picture of when you would use them. You’ll see that in most cases a unit-less number is an explicit detail, such as a specific column to position an element, a specific layer in a stacking context, a boolean that enables or disables a feature, or the order of elements. But note that anytime we declare “zero” as a number, we can write it with or without a prepended unit, as zero always evaluates to zero no matter what unit we’re dealing with.

border: 0; /* No border */
box-shadow: 0 0 5px #333; /* No shadow offset */
margin: 0; /* No margin */
padding: 0; /* No padding */

We can create our own custom units!

In some cases, we may want to work with a numeric value, but CSS doesn’t exactly recognize it as one. In these cases, the number is recognized as a “string” value instead, regardless of whether or not it contains alphabetical characters. That’s where we can use @property to create what’s called a “custom property” that evaluates a numeric value in a certain way.

Here’s a good example. There was a time when it was virtually impossible to animate a gradient that changes colors over time because to do so would require (1) a color function that allows us to change a color value’s hue (which we have with hsl()) and (2) being able to interpolate that hue value around the color spectrum, between a range of 0deg and 360deg.

Sounds simple enough, right? Define a variable that starts at 0 and then cycles through 360 degrees at the end of an animation. But this doesn’t work:

/* 👎 */
.element {
  --hue: 0;
  
  animation: rainbow 10s linear infinite;
  background: linear-gradient(hsl(--hue 50% 50%);
}

@keyframes rainbow {
  from { --hue: 0; }
  to { --hue: 360deg; }
}

That’s because CSS reads the variable as a string instead of a number. We have to register that variable as a custom property so that CSS aptly reads it as a numeric value.

@property --hue {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

There we go! Now that we’ve given CSS a hint that the --hue syntax is that of a <number>, we can animate away!

/* 👍 */
@property --hue {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

.element {
  --hue: 0;
  
  animation: rainbow 10s linear infinite;
  background: linear-gradient(hsl(--hue 50% 50%);
}

@keyframes rainbow {
  from { --huw: 0; }
  to { --hue: 360deg; }
}

Find a deeper explanation of this same example in “Interpolating Numeric CSS Variables” by Geoff Graham.


When to use one unit over another

This is super tricky because CSS is extremely flexible and there are no definitive or hard-and-fast rules for when to use a specific type of CSS length unit over another. In some cases, you absolutely have to use a specific unit because that’s how a certain CSS feature is specced, like using angle units to set the direction of a linear gradient:

.element {
  background: linear-gradient(
    135deg, red, blue;
  )
}

The same goes for the values we use in certain color functions, like using percentages to set the saturation and lightness levels in the hsl() function:

.element {
  background: hsl(0 100% 10%);
}

Speaking of color functions, we define alpha transparency with either an integer or number:

.element {
  background: hsl(0 100% 10% / 0.5); /* or simply .5 */
}

All that being said, many cases are going to be some degree of “it depends” but there are some instances where it makes sense to use a specific unit in your work.

Generally set font sizes in rem and em units

This way, you can set things up in a way where changing the font-size value of the <html> or a parent element updates the font sizes of their descendants.

html {
  font-size: 18px; /* Inherited by all other elements */
}

.parent {
  font-size: 1rem; /* Updates when the `html` size changes */
}

.child {
  font-size: 1em; /* Updates when the parent size changes */
}

Declare “zero” without units if you’d like

It’s not a big deal or anything, but leaving off the units shortens the code a smidge, and anytime we’re able to write shorter code it’s an opportunity for faster page performance. The impact may be negligible, but we’re able to do it since 0 always computes to 0, no matter what unit we’re working with.

Use container units for responsive design, where possible

Container queries in general are so gosh-darn great for responsive layouts because they look at the size of the container and let us apply styles to its descendants when the container is a certain size.

.parent {
  container: my-container / inline-size; /* Looks at width */
}

.child {
  display: flex;
  flex-direction: column;
  max-width: 100vh; /* 100% of the viewport */
}

/* When the container is greater than 600px wide */
@container my-container (width >= 600px) {
  .child {
    flex-direction: row;
    max-width: 50%; /* 50% of the parent elenent */
  }
}

So, if we going to size the .child element — or any of its children — it’s worth specifying sizes in relation to the container’s size with container units than, say, the viewport’s size with viewport units.

.parent {
  container: my-container / inline-size; /* Looks at width */
}

.child {
  display: flex;
  flex-direction: column;
  max-width: 100cqi; /* 100% of the container */
}

/* When the container is greater than 600px wide */
@container my-container (width >= 600px) {
  .child {
    flex-direction: row;
    max-width: 50cqi; /* 50% of the container */
  }
}

Use percentages when you’re unsure of the context

Yes, use container units for responsive design, but that only does you good if you know you are in the context of a container. It’s possible, though, that you use the same component in different places, and one of those places might not be a registered container.

In that case, go with a percentage value because percentages are relative to whatever parent element you’re in, regardless of whether or not it’s a container. This way, you can declare an element’s size as 100% to take up the full space of whatever parent element contains it.

The only word of caution is to note that we’re only basing the size on the parent. Meanwhile, container units can style any descendant in the container, no matter where it is located.

Viewport units are great for laying out containers

You may be thinking that viewport units are a bad thing since we’ve been advising against them in so many cases, like font sizing, but they are still incredibly useful, particularly when it comes to establishing a full-page layout.

I say “full-page” layout because container queries are the gold standard for sizing elements according to the space they have in their container. But if we’re working with a full page of containers, this is where viewport units can be used to establish a responsive layout at a higher level.

If the elements of individual containers look at their container for sizing information, then the sizing and placement of individual containers themselves probably ought to look at the viewport since it directly influences the amount of space on the page.

Examples

Element (em) and Relative element (rem) units

Let’s talk specifically about these two little buggers. We saw how a percentage unit calculates its size by the size of something else. em and rem units are sort of the same, but they are calculated based on the relative font size of specific elements.

Let’s start with em units and say we have an HTML element, a <div> with a .box class, and we set its font size to 20px. That means any text inside of that element is 20px.

Great, now what if we decide we need additional text in the box, and give it a relative font size of 1.5em?

See how a font size of 1.5em is larger than the 20px text? That’s because the larger text is based on the box’s font size. Behind the scenes, this is what the browser is calculating:

20px * 1.5 = 30px

So, the relative font size is multiplied by the pixel font size, which winds up being 30px.

And guess what? rem units do the exact same thing. But instead of multiplying itself by the pixel font size of the parent container, it looks at the pixel font size of the actual <html> element. By default, that is 16px.

/* This is the browser's default font size */
html {
  font-size: 16px;
}

.box {
  font-size: 1.5rem; /* 16px * 1.5 = 24px */
}

And if we change the HTML element’s font size to something else?

html {
  font-size: 18px;
}

.box {
  font-size: 1.5rem; /* 18px * 1.5 = 27px */
}
Character unit (ch)

The character unit (ch) is another is another unit relative to font size and, while it isn’t used all that often, it is amazingly great at sizing things based on the amount of content displayed in an element, because one character unit equals the width of one character of content. Here’s how I wrap my own head around it. If we have this in our HTML:

<p>The big brown dog lazily jumped over the fence.</p>

…and this CSS:

p {
  width: 10ch;
}

What we get is a paragraph that is 10 characters wide. That means the text will break after the tenth character, including spaces.

But note that the words themselves do not break. If the content is supposed to break after 10 characters, the browser will start the next line after a complete word instead of breaking the word into multiple lines, keeping everything easy to read.

If you’re wondering when you might reach for the ch unit, it’s absolutely boss at establishing line lengths that are more pleasant and legible, especially for long form content like this guide you’re reading.

Line height unit (lh)

The line-height unit (lh) looks at the line-height property value of the element’s containing (i.e., parent) element and uses that value to size things up.

.parent {
  line-height: 1.5;
}

.child {
  height: 3lh; /* 3 * 1.5 = 4.5 */
}

When would you use this? Personally, I find lh useful for setting an exact height on something based on the number of lines needed for the text. You can see this clearly demonstrated in Temani Afif’s “CSS Ribbon” effect that uses lh to establish dimensions for each row of text that makes for consistently-sized lines that adapt to whatever the parent element’s font-size value happens to be,


Absolute vs. Relative Units
Container Units
Viewport Units
Typography
Angle Units
Time Units
Resolution Units

CSS Length Units originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-length-units/feed/ 7 375815
Healthcare, Selling Lemons, and the Price of Developer Experience https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/ https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/#comments Thu, 09 Feb 2023 19:45:48 +0000 https://css-tricks.com/?p=377169 Every now and then, a one blog post is published and it spurs a reaction or response in others that are, in turn, published as blogs posts, and a theme starts to emerge. That’s what happened this past week and …


Healthcare, Selling Lemons, and the Price of Developer Experience originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Every now and then, a one blog post is published and it spurs a reaction or response in others that are, in turn, published as blogs posts, and a theme starts to emerge. That’s what happened this past week and the theme developed around the cost of JavaScript frameworks — a cost that, in this case, reveals just how darn important it is to use JavaScript responsibly.

Eric Bailey: Modern Health, frameworks, performance, and harm

This is where the story begins. Eric goes to a health service provider website to book an appointment and gets… a blank screen.

In addition to a terrifying amount of telemetry, Modern Health’s customer-facing experience is delivered using React and Webpack.

If you are familiar with how the web is built, what happened is pretty obvious: A website that over-relies on JavaScript to power its experience had its logic collide with one or more other errant pieces of logic that it summons. This created a deadlock.

If you do not make digital experiences for a living, what happened is not obvious at all. All you see is a tiny fake loading spinner that never stops.

D’oh. This might be mere nuisance — or even laughable — in some situations, but not when someone’s health is on the line:

A person seeking help in a time of crisis does not care about TypeScript, tree shaking, hot module replacement, A/B tests, burndown charts, NPS, OKRs, KPIs, or other startup jargon. Developer experience does not count for shit if the person using the thing they built can’t actually get what they need.

This is the big smack of reality. What happens when our tooling and reporting — the very things that are supposed to make our work more effective — get in the way of the user experience? These are tools that provide insights that can help us anticipate a user’s needs, especially in a time of need.

I realize that pointing the finger at JavaScript frameworks is already divisive. But this goes beyond whether you use React or framework d’jour. It’s about business priorities and developer experience conflicting with user experiences.

Alex Russell: The Market for Lemons

Partisans for slow, complex frameworks have successfully marketed lemons as the hot new thing, despite the pervasive failures in their wake, crowding out higher-quality options in the process.

These technologies were initially pitched on the back of “better user experiences”, but have utterly failed to deliver on that promise outside of the high-management-maturity organisations in which they were born. Transplanted into the wider web, these new stacks have proven to be expensive duds.

There’s the rub. Alex ain’t mincing words, but notice that the onus is on the way frameworks haved been marketed to developers than developers themselves. The sales pitch?

Once the lemon sellers embed the data-light idea that improved “Developer Experience” (“DX”) leads to better user outcomes, improving “DX” became and end unto itself, and many who knew better felt forced to play along. The long lead times in falsifying trickle-down UX was a feature, not a bug; they don’t need you to succeed, only to keep buying.

As marketing goes, the “DX” bait-and-switch is brilliant, but the tech isn’t delivering for anyone but developers.

Tough to stomach, right? No one wants to be duped, and it’s tough to admit a sunken cost when there is one. It gets downright personal if you’ve invested time in a specific piece of tech and effort integrating it into your stack. Development workflows are hard and settling into one is sorta like settling into a house you plan on living in a little while. But you’d want to know if your house was built on what Alex calls a “sandy foundation”.

I’d just like to pause here a moment to say I have no skin in this debate. As a web generalist, I tend to adopt new tools early for familiarity then drop them fast, relegating them to my toolshed until I find a good use for them. In other words, my knowledge is wide but not very deep in one area or thing. HTML, CSS, and JavaScript is my go-to cocktail, but I do care a great deal about user experience and know when to reach for a tool to solve a particular thing.

And let’s acknowledge that not everyone has a say in the matter. Many of us work on managed teams that are prescribed the tools we use. Alex says as much, which I think is important to call out because it’s clear this isn’t meant to be personal. It’s a statement on our priorities and making sure they along to user expectations.

Let’s alow Chris to steer us back to the story…

Chris Coyier: End-To-End Tests with Content Blockers?

So, maybe your app is built on React and it doesn’t matter why it’s that way. There’s still work to do to ensure the app is reliable and accessible.

Just blocking a file shouldn’t totally wreck a website, but it often does! In JavaScript, that may be because the developers have written first-party JavaScript (which I’ll generally allow) that depends on third-party JavaScript (which I’ll generally block).

[…]

If I block resources from tracking-website.com, now my first-party JavaScript is going to throw an error. JavaScript isn’t chill. If an error is thrown, it doesn’t execute more JavaScript further down in the file. If further down in that file is transitionToOnboarding();— that ain’t gonna work.

Maybe it’s worth revisiting your workflow and tweaking it to account to identify more points of failure.

So here’s an idea: Run your end-to-end tests in browsers that have popular content blockers with default configs installed. 

Doing so may uncover problems like this that stop your customers, and indeed people in need, from being stopped in their tracks.

Good idea! Hey, anything that helps paint a more realistic picture of how the app is used. That sort of clarity could happen a lot earlier in the process, perhaps before settling on development decisions. Know your users. Why are they using the app? How do they browse the web? Where are they phsically located? What problems could get in their way? Chris has a great talk on that, too.


Healthcare, Selling Lemons, and the Price of Developer Experience originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/healthcare-selling-lemons-and-the-price-of-developer-experience/feed/ 7 377169
The truth about CSS selector performance https://css-tricks.com/the-truth-about-css-selector-performance/ Tue, 07 Feb 2023 15:59:35 +0000 https://css-tricks.com/?p=376659 Geez, leave it to Patrick Brosset to talk CSS performance in the most approachable and practical way possible. Not that CSS is always what’s gunking up the speed, or even the lowest hanging fruit when it comes to improving …


The truth about CSS selector performance originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Geez, leave it to Patrick Brosset to talk CSS performance in the most approachable and practical way possible. Not that CSS is always what’s gunking up the speed, or even the lowest hanging fruit when it comes to improving performance.

But if you’re looking for gains on the CSS side of things, Patrick has a nice way of sniffing out your most expensive selectors using Edge DevTools:

  • Crack open DevTools.
  • Head to the Performance Tab.
  • Make sure you have the “Enable advanced rendering instrumentation” option enabled. This tripped me up in the process.
  • Record a page load.
  • Open up the “Bottom-Up” tab in the report.
  • Check out your the size of your recalculated styles.
DevTools with Performance tab open and a summary of events.

From here, click on one of the Recalculated Style events in the Main waterfall view and you’ll get a new “Selector Stats” tab. Look at all that gooey goodness!

Now you see all of the selectors that were processed and they can be sorted by how long they took, how many times they matched, the number of matching attempts, and something called “fast reject count” which I learned is the number of elements that were easy and quick to eliminate from matching.

A lot of insights here if CSS is really the bottleneck that needs investigating. But read Patrick’s full post over on the Microsoft Edge Blog because he goes much deeper into the why’s and how’s, and walks through an entire case study.

To Shared LinkPermalink on CSS-Tricks


The truth about CSS selector performance originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
376659