Css Tricks

Syndicate content CSS-Tricks
Tips, Tricks, and Techniques on using Cascading Style Sheets.
Updated: 11 hours 43 min ago

Alvaro Montoro: CSS One-Liners to Improve (Almost) Every Project

Mon, 07/22/2024 - 4:38am

These sorts of roundups always get me. My wife will flip through Zillow photos of the insides of homes for hours because she likes seeing how different people decorate, Feng Shui, or what have you. That’s her little dip into Voyeur-Land. Mine? It could easily be scrolling through CSS snippets that devs keep within arm’s reach.

Alvaro was kind enough to share the trustiest of his trusty CSS:

  1. Limit the content width within the viewport
  2. Increase the body text size
  3. Increase the line between rows of text
  4. Limit the width of images
  5. Limit the width of text within the content
  6. Wrap headings in a more balanced way
  7. Form control colors to match page styles
  8. Easy-to-follow table rows
  9. Spacing in table cells and headings
  10. Reduce animations and movement

Not dropping the snippets in here (it’s worth reading the full post for that). But I do have a couple of my own that I’d tack on. And like Alvaro says up-front about his list, not all of these will be 100% applicable to every project.

Global border-box sizing

No explanation needed here. It’s often the very first thing declared in any given stylesheet on the web.

*, *::before, *::after { box-sizing: border-box; }

I’m guessing Alvaro uses this, too, and maybe it’s too obvious to list. Or maybe it’s more of a DX enhancement that belongs in a reset more than it is something that improves the website.

System fonts

Default text on the web is just so… so… so blah. I love that Alvaro agrees that 16px is way too small to be the web’s default font-size for text. I would take that one step further and wipe out the Times New Roman default font as well. I’m sure there are sites out there leveraging it (I did on my own personal site for years as an act of brutal minimalism), but a personal preference these days is defaulting to whatever the OS default font is.

body { font-family: system-ui; }

We can be a little more opinionated than that by falling back to either a default serif or sans-serif font.

body { font-family: system-ui, sans-serif; }

There are much, much more robust approaches for sure, but this baseline is a nice starting point for just about any site.

Cut horizontal overflow from the <body>

Oh gosh, I never ever make this mistake. &#x1f61d;

But hypothetically, if I did — and that’s a BIG if — I like preventing it from messing with a visitor’s scrolling experience. Once the <body>‘s intrinsic width is forced outside the viewport, we get horizontal scrolling that might be a very cool thing if it’s intentional but is not-so-bueno when it’s not.

body { overflow-x: hidden; }

I’ll use this as a defensive mechanism but would never want to rely on it as an actual solution to the possible loss of data that comes with overflowing content. This merely masks the problem while allowing an opportunity to fix the root cause without visitors having to deal with the rendered consequences.

Give the <body> some breathing room

Not too much, not too little, but the baby bear porridge just the right amount of space to keep content from hugging right up to the edges.

body { padding-block: 15px; }

To Shared LinkPermalink on CSS-Tricks

Alvaro Montoro: CSS One-Liners to Improve (Almost) Every Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Sara Joy: Everybody’s Free (To Write Websites)

Wed, 07/17/2024 - 8:36am

Sara Joy’s adaptation of the song “Everybody’s Free (To Wear Sunscreen)” (YouTube) originally by Baz Luhrman with lyrics pulled directly from Mary Schmich‘s classic essay, “Wear Sunscreen”. Anyone who has graduated high school since 1999 doesn’t even have to look up the song since it’s become an unofficial-official commencement ceremony staple. If you graduated in ’99, then I’m sorry. You might still be receiving ongoing treatment for the earworm infection from that catchy tune spinning endlessly on radio (yes, radio). Then again, those of us from those late-90’s classes came down with more serious earworm cases from the “I Will Remember You” and “Time of Your Life” outbreaks.

Some choice pieces of Sara’s “web version”:

Don’t feel guilty if you don’t know what you want to do with your site. The most interesting websites don’t even have an introduction, never mind any blog posts. Some of the most interesting web sites I enjoy just are.

Add plenty of semantic HTML.

Clever play on words and selectors:

Enjoy your <body>. Style it every way you can. Don’t be afraid of CSS, or what other people think of it. It’s the greatest design tool you’ll ever learn.

The time’s they are a-changin’:

Accept certain inalienable truths: connection speeds will rise, techbros will grift, you too will get old— and when you do, you’ll fantasize that when you were young websites were light-weight, tech founders were noble and fonts used to be bigger.

And, of course:

Respect the W3C.

Oh, and remember: Just build websites.

To Shared LinkPermalink on CSS-Tricks

Sara Joy: Everybody’s Free (To Write Websites) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS Selectors

Mon, 07/15/2024 - 6:13am

CSS is really good at many things, but it’s really, really good at two specific things: selecting elements and styling them. That’s the raison d’être for CSS and why it’s a core web language. In this guide, we will cover the different ways to select elements — because the styles we write are pretty much useless without the ability to select which elements to apply them to.

The source of truth for CSS selectors is documented in the Selectors Module Level 4 specification. With one exception (which we’ll get to), all of the selectors covered here are well-covered by browsers across the board, and most certainly by all modern browsers.

In addition to selectors, this guide also looks at CSS combinators. If selectors identify what we are selecting, you might think of combinators as how the styles are applied. Combinators are like additional instructions we give CSS to select a very particular element on the page, not totally unlike the way we can use filters in search engines to find the exact result we want.

Quick reference Common Selectors /* Universal */ * { box-sizing: border-box; } /* Type or Tag */ p { margin-block: 1.5rem; } /* Classname */ .class { text-decoration: underline; } /* ID */ #id { font-family: monospace; } /* Relational */ li:has(a) { display: flex; } Common Combinators /* Descendant */ header h1 { /* Selects all Heading 1 elements in a Header element. */ } /* Child */ header > h1 { /* Selects all Heading 1 elements that are children of Header elements. */ } /* General sibling */ h1 ~ p { /* Selects a Paragraph as long as it follows a Heading 1. */ } /* Adjacent sibling */ h1 + p { /* Selects a Paragraph if it immediately follows a Heading 1 */ } /* Chained */ h1, p { /* Selects both elements. */ } General Selectors

When we talk about CSS selectors, we’re talking about the first part of a CSS ruleset:

/* CSS Ruleset */ selector { /* Style rule */ property: value; }

See that selector? That can be as simple as the HTML tag we want to select. For example, let’s select all <article> elements on a given page.

/* Select all <article> elements... */ article { /* ... and apply this background-color on them */ background-color: hsl(25 100% 50%); }

That’s the general process of selecting elements to apply styles to them. Selecting an element by its HTML tag is merely one selector type of several. Let’s see what those are in the following section.

Element selectors

Element selectors are exactly the type of selector we looked at in that last example: Select the element’s HTML tag and start styling!

That’s great and all, but consider this: Do you actually want to select all of the <article> elements on the page? That’s what we’re doing when we select an element by its tag — any and all HTML elements matching that tag get the styles. The following demo selects all <article> elements on the page, then applies a white (#fff) background to them. Notice how all three articles get the white background even though we only wrote one selector.

CodePen Embed Fallback

I’ve tried to make it so the relevant for code for this and other demos in this guide is provided at the top of the CSS tab. Anything in a @layer can be ignored. And if you’re new to @layer, you can learn all about it in our CSS Cascade Layers guide.

But maybe what we actually want is for the first element to have a different background — maybe it’s a featured piece of content and we need to make it stand out from the other articles. That requires us to be more specific in the type of selector we use to apply the styles.

Let’s turn our attention to other selector types that allow us to be more specific about what we’re selecting.

ID selectors

ID selectors are one way we can select one element without selecting another of the same element type. Let’s say we were to update the HTML in our <article> example so that the first article is “tagged” with an ID:

<article id="featured"> <!-- Article 1 --> </article> <article> <!-- Article 2 --> </article> <article> <!-- Article 3 --> </article>

Now we can use that ID to differentiate that first article from the others and apply styles specifically to it. We prepend a hashtag character (#) to the ID name when writing our CSS selector to properly select it.

/* Selects all <article> elements */ article { background: #fff; } /* Selects any element with id="featured" */ #featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); }

There we go, that makes the first article pop a little more than the others!

CodePen Embed Fallback

Before you go running out and adding IDs all over your HTML, be aware that IDs are considered a heavy-handed approach to selecting. IDs are so specific, that it is tough to override them with other styles in your CSS. IDs have so much specificity power than any selector trying to override it needs at least an ID as well. Once you’ve reached near the top of the ladder of this specificity war, it tends to lead to using !important rules and such that are in turn nearly impossible to override.

Let’s rearrange our CSS from that last example to see that in action:

/* Selects any element with id="featured" */ #featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); } /* Selects all <article> elements */ article { background: #fff; }

The ID selector now comes before the element selector. According to how the CSS Cascade determines styles, you might expect that the article elements all get a white background since that ruleset comes after the ID selector ruleset. But that’s not what happens.

CodePen Embed Fallback

So, you see how IDs might be a little too “specific” when it comes to selecting elements because it affects the order in which the CSS Cascade applies styles and that makes styles more difficult to manage and maintain.

The other reason to avoid IDs as selectors? We’re technically only allowed to use an ID once on a page, per ID. In other words, we can have one element with #featured but not two. That severely limits what we’re able to style if we need to extend those styles to other elements — not even getting into the difficulty of overriding the ID’s styles.

A better use case for IDs is for selecting items in JavaScript — not only does that prevent the sort of style conflict we saw above, but it helps maintain a separation of concerns between what we select in CSS for styling versus what we select in JavaScript for interaction.

Another thing about ID selectors: The ID establishes what we call an “anchor” which is a fancy term for saying we can link directly to an element on the page. For example, if we have an article with an ID assigned to it:

<article id="featured">...</article>

…then we can create a link to it like this:

<a href="featured">Jump to article below ⬇️</a> <!-- muuuuuuch further down the page. --> <article id="featured">...</article>

Clicking the link will navigate you to the element as though the link is anchored to that element. Try doing exactly that in the following demo:

CodePen Embed Fallback

This little HTML goodie opens up some pretty darn interesting possibilities when we sprinkle in a little CSS. Here are a few articles to explore those possibilities.

Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Class selectors

Class selectors might be the most commonly used type of CSS selector you will see around the web. Classes are ideal because they are slightly more specific than element selectors but without the heavy-handedness of IDs. You can read a deep explanation of how the CSS Cascade determines specificity, but the following is an abbreviated illustration focusing specifically (get it?!) on the selector types we’ve looked at so far.

That’s what makes class selectors so popular — they’re only slightly more specific than elements, but keep specificity low enough to be manageable if we need to override the styles in one ruleset with styles in another.

The only difference when writing a class is that we prepend a period (.) in front of the class name instead of the hashtag (#).

/* Selects all <article> elements */ article { background: #fff; } /* Selects any element with class="featured" */ .featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); }

Here’s how our <article> example shapes up when we swap out #featured with .featured.

CodePen Embed Fallback

Same result, better specificity. And, yes, we can absolutely combine different selector types on the same element:

<article id="someID" class="featured">...</article>

Do you see all of the possibilities we have to select an <article>? We can select it by:

  • Its element type (article)
  • Its ID (#someID)
  • Its class (.featured)

The following articles will give you some clever ideas for using class selectors in CSS.

Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier

But we have even more ways to select elements like this, so let’s continue.

Attribute selectors

ID and class selectors technically fall into this attribute selectors category. We call them “attributes” because they are present in the HTML and give more context about the element. All of the following are attributes in HTML:

<!-- ID, Class, Data Attribute --> <article id="#id" class=".class" data-attribute="attribute"> </article> <!-- href, Title, Target --> <a href="https://css-tricks.com" title="Visit CSS-Tricks" target="_blank"></a> <!-- src, Width, Height, Loading --> <img src="star.svg" width="250" height="250" loading="laxy" > <!-- Type, ID, Name, Checked --> <input type="checkbox" id="consent" name="consent" checked /> <!-- Class, Role, Aria Label --> <div class="buttons" role="tablist" aria-label="Tab Buttons">

Anything with an equals sign (=) followed by a value in that example code is an attribute. So, we can technically style all links with an href attribute equal to https://css-tricks.com:

a[href="https://css-tricks.com"] { color: orangered; }

Notice the syntax? We’re using square brackets ([]) to select an attribute instead of a period or hashtag as we do with classes and IDs, respectively.

CodePen Embed Fallback

The equals sign used in attributes suggests that there’s more we can do to select elements besides matching something that’s exactly equal to the value. That is indeed the case. For example, we can make sure that the matching selector is capitalized or not. A good use for that could be selecting elements with the href attribute as long as they do not contain uppercase letters:

/* Case sensitive */ a[href*='css-tricks' s] {}

The s in there tells CSS that we only want to select a link with an href attribute that does not contain uppercase letters.

<!-- &#x1f44e; No match --> <a href="https://CSS-Tricks.com">...</a> <!-- &#x1f44d; Match! --> <a href="https://css-tricks.com">...</a>

If case sensitivity isn’t a big deal, we can tell CSS that as well:

/* Case insensitive */ a[href*='css-tricks' i] {}

Now, either one of the link examples will match regardless of there being upper- or lowercase letters in the href attribute.

<!-- &#x1f44d; I match! --> <a href="https://CSS-Tricks.com">...</a> <!-- &#x1f44d; I match too! --> <a href="https://css-tricks.com">...</a>

There are many, many different types of HTML attributes. Be sure to check out our Data Attributes guide for a complete rundown of not only [data-attribute] but how they relate to other attributes and how to style them with CSS.

Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Jakob E Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Universal selector

CSS-Tricks has a special relationship with the Universal Selector — it’s our logo!

That’s right, the asterisk symbol (*) is a selector all unto itself whose purpose is to select all the things. Quite literally, we can select everything on a page — every single element — with that one little asterisk. Note I said every single element, so this won’t pick up things like IDs, classes, or even pseudo-elements. It’s the element selector for selecting all elements.

/* Select ALL THE THINGS! &#x1f4a5; */ * { /* Styles */ }

Or, we can use it with another selector type to select everything inside a specific element.

/* Select everything in an <article> */ article * { /* Styles */ }

That is a handy way to select everything in an <article>, even in the future if you decide to add other elements inside that element to the HTML. The times you’ll see the Universal Selector used most is to set border-sizing on all elements across the board, including all elements and pseudo-elements.

*, *::before, *::after { box-sizing: border-box; }

There’s a good reason this snippet of CSS winds up in so many stylesheets, which you can read all about in the following articles.

Article on Jul 22, 2024 CSS Selectors Sara Cope Article on Jul 22, 2024 CSS Selectors Marie Mosley Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier

Sometimes the Universal Selector is implied. For example, when using a pseudo selector at the start of a new selector. These are selecting exactly the same:

*:has(article) { } :has(article) { } Pseudo-selectors

Pseudo-selectors are for selecting pseudo-elements, just as element selectors are for selecting elements. And a pseudo-element is just like an element, but it doesn’t actually show up in the HTML. If pseudo-elements are new to you, we have a quick explainer you can reference.

Every element has a ::before and ::after pseudo-element attached to it even though we can’t see it in the HTML.

<div class="container"> <!-- ::before psuedo-element here --> <div>Item</div> <div>Item</div> <div>Item</div> <!-- ::after psuedo-element here --> </div>

These are super handy because they’re additional ways we can hook into an element an apply additional styles without adding more markup to the HTML. Keep things as clean as possible, right?!

We know that ::before and ::after are pseudo-elements because they are preceded by a pair of colons (::). That’s how we select them, too!

.container::before { /* Styles */ }

The ::before and ::after pseudo-elements can also be written with a single colon — i.e., :before and :after — but it’s still more common to see a double colon because it helps distinguish pseudo-elements from pseudo-classes.

But there’s a catch when using pseudo-selectors: they require the content property. That’s because pseudos aren’t “real” elements but ones that do not exist as far as HTML is concerned. That means they need content that can be displayed… even if it’s empty content:

.container::before { content: ""; }

Of course, if we were to supply words in the content property, those would be displayed on the page.

CodePen Embed Fallback Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Habdul Hazeez Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Geoff Graham Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Robin Rendle Article on Jul 22, 2024 CSS Selectors Dan Wilson Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Complex selectors

Complex selectors may need a little marketing help because “complex” is an awfully scary term to come across when you’re in the beginning stages of learning this stuff. While selectors can indeed become complex and messy, the general idea is super straightforward: we can combine multiple selectors in the same ruleset.

Let’s look at three different routes we have for writing these “not-so-complex” complex selectors.

Listing selectors

First off, it’s possible to combine selectors so that they share the same set of styles. All we do is separate each selector with a comma.

.selector-1, .selector-2, .selector-3 { /* We share these styles! &#x1f917; */ }

You’ll see this often when styling headings — which tend to share the same general styling except, perhaps, for font-size.

h1, h2, h3, h4, h5, h6 { color: hsl(25 80% 15%); font-family: "Poppins", system-ui; }

Adding a line break between selectors can make things more legible. You can probably imagine how complex and messy this might get. Here’s one, for example:

section h1, section h2, section h3, section h4, section h5, section h6, article h1, article h2, article h3, article h4, article h5, article h6, aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 { color: #BADA55; }

Ummmm, okay. No one wants this in their stylesheet. It’s tough to tell what exactly is being selected, right?

The good news is that we have modern ways of combining these selectors more efficiently, such as the :is() pseudo selector. In this example, notice that we’re technically selecting all of the same elements. If we were to take out the four section, article, aside, and nav element selectors and left the descendants in place, we’d have this:

h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, { color: #BADA55; }

The only difference is which element those headings are scoped to. This is where :is() comes in handy because we can match those four elements like this:

:is(section, article, aside, nav) { color: #BADA55; }

That will apply color to the elements themselves, but what we want is to apply it to the headings. Instead of listing those out for each heading, we can reach for :is() again to select them in one fell swoop:

/* Matches any of the following headings scoped to any of the following elements. */ :is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) { color: #BADA55; }

While we’re talking about :is() it’s worth noting that we have the :where() pseudo selector as well and that it does the exact same thing as :is(). The difference? The specificity of :is() will equal the specificity of the most specific element in the list. Meanwhile, :where() maintains zero specificity. So, if you want a complex selector like this that’s easier to override, go with :where() instead.

Article on Jul 22, 2024 CSS Selectors Geoff Graham Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Chris Coyier Article on Jul 22, 2024 CSS Selectors Šime Vidas Article on Jul 22, 2024 CSS Selectors Mojtaba Seyedi

Nesting selectors

That last example showing how :is() can be used to write more efficient complex selectors is good, but we can do even better now that CSS nesting is a widely supported feature.

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

DesktopChromeFirefoxIEEdgeSafari120117No12017.2Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari12612712617.2

CSS nesting allows us to better see the relationship between selectors. You know how we can clearly see the relationship between elements in HTML when we indent descendant elements?

<!-- Parent --> <article> <!-- Child --> <img src="" alt="..."> <!-- Child --> <div class="article-content"> <!-- Grandchild --> <h2>Title</h2> <!-- Grandchild --> <p>Article content.</p> </div> </article>

CSS nesting is a similar way that we can format CSS rulesets. We start with a parent ruleset and then embed descendant rulesets inside. So, if we were to select the <h2> element in that last HTML example, we might write a descendant selector like this:

article h2 { /* Styles */ }

With nesting:

article { /* Article styles */ h2 { /* Heading 2 styles */ } }

You probably noticed that we can technically go one level deeper since the heading is contained in another .article-content element:

article { /* Article styles */ .article-content { /* Container styles */ h2 { /* Heading 2 styles */ } } }

So, all said and done, selecting the heading with nesting is the equivalent of writing a descendant selector in a flat structure:

article .article-content h2 { /* Heading 2 styles */ }

You might be wondering how the heck it’s possible to write a chained selector in a nesting format. I mean, we could easily nest a chained selector inside another selector:

article { /* Article styles */ h2.article-content { /* Heading 2 styles */ } }

But it’s not like we can re-declare the article element selector as a nested selector:

article { /* Article styles */ /* Nope! &#x1f44e; */ article.article-element { /* Container styles */ /* Nope! &#x1f44e; */ h2.article-content { /* Heading 2 styles */ } } }

Even if we could do that, it sort of defeats the purpose of a neatly organized nest that shows the relationships between selectors. Instead, we can use the ampersand (&) symbol to represent the selector that we’re nesting into. We call this the nesting selector.

article { &.article-content { /* Equates to: article.article-content */ } } Compounding selectors

We’ve talked quite a bit about the Cascade and how it determines which styles to apply to matching selectors using a specificity score. We saw earlier how an element selector is less specific than a class selector, which is less specific than an ID selector, and so on.

article { /* Specificity: 0, 0, 1 */ } .featured { /* Specificity: 0, 1, 0 */ } #featured { /* Specificity: 1, 0, 0 */ }

Well, we can increase specificity by chaining — or “compounding” — selectors together. This way, we give our selector a higher priority when it comes to evaluating two or more matching styles. Again, overriding ID selectors is incredibly difficult so we’ll work with the element and class selectors to illustrate chained selectors.

We can chain our article element selector with our .featured class selector to generate a higher specificity score.

article { /* Specificity: 0, 0, 1 */ } .featured { /* Specificity: 0, 1, 0 */ } articie.featured { /* Specificity: 0, 1, 1 */ }

This new compound selector is more specific (and powerful!) than the other two individual selectors. Notice in the following demo how the compound selector comes before the two individual selectors in the CSS yet still beats them when the Cascade evaluates their specificity scores.

CodePen Embed Fallback

Interestingly, we can use “fake” classes in chained selectors as a strategy for managing specificity. Take this real-life example:

.wp-block-theme-button .button:not(.specificity):not(.extra-specificity) { }

Whoa, right? There’s a lot going on there. But the idea is this: the .specificity and .extra-specificity class selectors are only there to bump up the specificity of the .wp-block-theme .button descendant selector. Let’s compare the specificity score with and without those artificial classes (that are :not() included in the match).

.wp-block-theme-button .button { /* Specificity: 0, 2, 0 */ } .wp-block-theme-button .button:not(.specificity) { /* Specificity: 0, 3, 0 */ } .wp-block-theme-button .button:not(.specificity):not(.extra-specificity { /* Specificity: 0, 4, 0 */ }

Interesting! I’m not sure if I would use this in my own CSS but it is a less heavy-handed approach than resorting to the !important keyword, which is just as tough to override as an ID selector.


If selectors are “what” we select in CSS, then you might think of CSS combinators as “how” we select them. they’re used to write selectors that combine other selectors in order to target elements. Inception!

The name “combinator” is excellent because it accurately conveys the many different ways we’re able to combine selectors. Why would we need to combine selectors? As we discussed earlier with Chained Selectors, there are two common situations where we’d want to do that:

  • When we want to increase the specificity of what is selected.
  • When we want to select an element based on a condition.

Let’s go over the many types of combinators that are available in CSS to account for those two situations in addition to chained selectors.

Descendant combinator

We call it a “descendant” combinator because we use it to select elements inside other elements, sorta like this:

/* Selects all elements in .parent with .child class */ .parent .child {}

…which would select all of the elements with the .child class in the following HTML example:

<div class="parent"> <div class="child"></div> <div class="child"></div> <div class="friend"></div> <div class="child"></div> <div class="child"></div> </div>

See that element with the .friend classname? That’s the only element inside of the .parent element that is not selected with the .parent .child {} descendant combinator since it does not match .child even though it is also a descendant of the .parent element.

Child combinator

A child combinator is really just an offshoot of the descendant combinator, only it is more specific than the descendant combinator because it only selects direct children of an element, rather than any descendant.

Let’s revise the last HTML example we looked at by introducing a descendant element that goes deeper into the family tree, like a .grandchild:

<div class="parent"> <div class="child"></div> <div class="child"> <div class="grandchild"></div> </div> <div class="child"></div> <div class="child"></div> </div>

So, what we have is a .parent to four .child elements, one of which contains a .grandchild element inside of it.

Maybe we want to select the .child element without inadvertently selecting the second .child element’s .grandchild. That’s what a child combinator can do. All of the following child combinators would accomplish the same thing:

/* Select only the "direct" children of .parent */ .parent > .child {} .parent > div {} .parent > * {}

See how we’re combining different selector types to make a selection? We’re combinating, dangit! We’re just doing it in slightly different ways based on the type of child selector we’re combining.

/* Select only the "direct" children of .parent */ .parent > #child { /* direct child with #child ID */ .parent > .child { /* direct child with .child class */ } .parent > div { /* direct child div elements */ } .parent > * { /* all direct child elements */ }

It’s pretty darn neat that we not only have a way to select only the direct children of an element, but be more or less specific about it based on the type of selector. For example, the ID selector is more specific than the class selector, which is more specific than the element selector, and so on.

General sibling combinator

If two elements share the same parent element, that makes them siblings like brother and sister. We saw an example of this in passing when discussing the descendant combinator. Let’s revise the class names from that example to make the sibling relationship a little clearer:

<div class="parent"> <div class="brother"></div> <div class="sister"></div> </div>

This is how we can select the .sister element as long as it is preceded by a sibling with class .brother.

/* Select .sister only if follows .brother */ .brother ~ .sister { }

The Tilda symbol (~) is what tells us this is a sibling combinator.

It doesn’t matter if a .sister comes immediately after a .brother or not — as long as a .sister comes after a brother and they share the same parent element, it will be selected. Let’s see a more complicated HTML example:

<main class="parent"> <!-- .sister immediately after .brother --> <div class="brother"></div> <div class="sister"></div> <!-- .sister immediately after .brother --> <div class="brother"></div> <div class="sister"></div> <!-- .sister immediately after .sister --> <div class="sister"></div> <!-- .cousin immediately after .brother --> <div class="brother"></div> <div class="cousin"> <!-- .sister contained in a .cousin --> <div class="sister"></div> </div> </main>

The sibling combinator we wrote only selects the first three .sister elements because they are the only ones that come after a .brother element and share the same parent — even in the case of the third .sister which comes after another sister! The fourth .sister is contained inside of a .cousin, which prevents it from matching the selector.

Let’s see this in context. So, we can select all of the elements with an element selector since each element in the HTML is a div:

CodePen Embed Fallback

From there, we can select just the brothers with a class selector to give them a different background color:

CodePen Embed Fallback

We can also use a class selector to set a different background color on all of the elements with a .sister class:

CodePen Embed Fallback

And, finally, we can use a general sibling combinator to select only sisters that are directly after a brother.

CodePen Embed Fallback

Did you notice how the last .sister element’s background color remained green while the others became purple? That’s because it’s the only .sister in the bunch that does not share the same .parent as a .brother element.

Adjacent combinator

Believe it or not, we can get even more specific about what elements we select with an adjacent combinator. The general sibling selector we just looked at will select all of the .sister elements on the page as long as it shares the same parent as .brother and comes after the .brother.

What makes an adjacent combinator different is that it selects any element immediately following another. Remember how the last .sister didn’t match because it is contained in a different parent element (i.e., .cousin)? Well, we can indeed select it by itself using an adjacent combinator:

/* Select .sister only if directly follows .brother */ .brother + .sister { }

Notice what happens when we add that to our last example:

CodePen Embed Fallback

The first two .sister elements changed color! That’s because they are the only sisters that come immediately after a .brother. The third .sister comes immediately after another .sister and the fourth one is contained in a .cousin which prevents both of them from matching the selection.

Learn more about CSS selectors Table of contents References

The vast majority of what you’re reading here is information pulled from articles we’ve published on CSS-Tricks and those are linked up throughout the guide. In addition to those articles, the following resources were super helpful for putting this guide together.

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

“If” CSS Gets Inline Conditionals

Tue, 07/09/2024 - 5:18am

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:

A historical day for CSS &#x1f600;&#x1f389;

If you write any components used and/or styled by others, you know how huge this is!

background: if(style(–variant: success), var(–green));

Even if you don’t, this will allow things like:
padding: if(var(–2xl), 1em, var(–xl) or var(–m),… pic.twitter.com/cXeqwBuXvK

— Lea Verou (@LeaVerou) June 13, 2024

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.


It boils down to this:

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


  • 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.

This is all related to style queries

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 valuesProsConsBinary Linear InterpolationNumbersQuantitativeCan be used as part of a valueLimited output rangeTogglesvar(--alias) (actual values are too weird to expose raw)AnyCan be used in part of a valueWeird values that need to be aliasedPaused animationsNumbersAnyNormal, decoupled declarationsTakes over animation property

Cascade weirdnessType 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! &#x1f382;


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.

Poppin’ In

Wed, 06/26/2024 - 6:37am

Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello. &#x1f44b;

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:


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.

CodePen Embed Fallback

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

CodePen Embed Fallback

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.

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">&#x1f44b;</div>

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

CodePen Embed Fallback

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 &#x1f44e; */ #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.

CodePen Embed Fallback

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.

CodePen Embed Fallback

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.

CodePen Embed Fallback

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.

CodePen Embed Fallback

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?

CodePen Embed Fallback

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.

<p>CSS Meditation #8:<code> .work +

Mon, 06/24/2024 - 8:28am

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.

<p>CSS Meditation #7: Nobody is perf

Mon, 06/24/2024 - 8:27am

CSS Meditation #7: Nobody is perf-ect.

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

<p>CSS Meditation #6: The color space

Mon, 06/24/2024 - 8:27am

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.

<p>CSS Meditation #5: <code>:where(:is(

Mon, 06/24/2024 - 8:26am

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

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

<p>CSS Meditation #4: Select, style,

Mon, 06/24/2024 - 8:26am

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.

<p>CSS Meditation #3: A pseudo is as a

Mon, 06/24/2024 - 8:25am

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.

<p>CSS Meditation #2: Who gives a

Mon, 06/24/2024 - 8:25am

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.

<p>CSS Meditation #1: If the code works

Mon, 06/24/2024 - 8:22am

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 Container Queries

Mon, 06/10/2024 - 6:12am

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.
Table of Contents 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 default, none, at, no, and or. Note that the names are not wrapped in quotes.
Open in Almanac
  • 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.
Open in Almanac
  • 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.

Open in Almanac
  • 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 widthcqhContainer 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.

DesktopChromeFirefoxIEEdgeSafari129NoNo126TPMobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari126No12618.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.

DesktopChromeFirefoxIEEdgeSafari106110No10616.0Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari12612712616.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.

CodePen Embed Fallback 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.

CodePen Embed Fallback 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.

CodePen Embed Fallback 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.

CodePen Embed Fallback 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.

CodePen Embed Fallback Articles & Tutorials General Information Article on Jun 14, 2024 CSS Container Queries Robin Rendle Article on Jun 14, 2024 CSS Container Queries Robin Rendle Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Una Kravets Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Mathias Hülsbusch

Container Size Query Tutorials Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Jhey Tompkins Article on Jun 14, 2024 CSS Container Queries Dan Christofi Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier

Container Style Queries Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham

Almanac References Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham

Related Guides Article on Jun 14, 2024 CSS Container Queries Andrés Galante Article on Jun 14, 2024 CSS Container Queries Chris Coyier


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

Demystifying Screen Readers: Accessible Forms & Best Practices

Fri, 04/19/2024 - 4:26am

This is the 3rd post in a small series we did on form accessibility. If you missed the second post, check out “Managing User Focus with :focus-visible“. In this post we are going to look at using a screen reader when navigating a form, and also some best practices.

Editor’s Note: Edits were made throughout in regard to some of the best practices and code sample additions. If you have ideas and feedback to build on this post, please let us know!

What is a Screen Reader?

You may have heard the term “screen reader” as you have been moving around the web. You might even be using a screen reader at this moment to run manual accessibility tests on the experiences you are building. A screen reader is a type of AT or assistive technology.

A screen reader converts digital text into synthesized speech or Braille output, commonly seen with a Braille reader.


In this example, I will be using Mac VO. Mac VO (VoiceOver) is built-in to all Mac devices; iOS, iPadOS, and macOS systems. Depending on the type of device you are running macOS on, opening VO could differ. The Macbook Pro that is running VO I am writing this on doesn’t have the touch bar, so I will be using the shortcut keys according to the hardware.

Spinning Up VO on macOS

If you are using an updated Macbook Pro, the keyboard on your machine will look something like the image below.

You will start by holding down the cmd key and then pressing the Touch ID three times quickly.

If you are on a MBP (MacBook Pro) with a TouchBar, you will use the shortcut cmd+fn+f5 to turn on VO. If you are using a traditional keyboard with your desktop or laptop, the keys should be the same or you will have to toggle VO on in the Accessibility settings.. Once VO is turned on, you will be greeted with this dialog along with a vocalized introduction to VO.

If you click the “Use VoiceOver” button you are well on your way to using VO to test your websites and apps. One thing to keep in mind is that VO is optimized for use with Safari. That being said, make sure when you are running your screen reader test that Safari is the browser you are using. That goes for the iPhone and iPad as well.

There are two main ways you can use VO from the start. The way I personally use it is by navigating to a website and using a combination of the tab, control, option, shift and arrow keys, I can navigate through the experience efficiently with these keys alone.

Another common way to navigate the experience is by using the VoiceOver Rotor. The Rotor is a feature designed to navigate directly to where you want to be in the experience. By using the Rotor, you eliminate having to traverse through the whole site, think of it as a “Choose Your Own Adventure”.

Modifier Keys

Modifier keys are the way you use the different features in VO. The default modifier key or VO is control + option but you can change it to caps lock or choose both options to use interchangeably.

Using the Rotor

In order to use the Rotor you have to use a combination of your modifier key(s) and the letter “U”. For me, my modifier key is caps lock. I press caps lock + U and the Rotor spins up for me. Once the Rotor comes up I can navigate to any part of the experience that I want using the left and right arrows.

Using the Rotor in VoiceOver Navigating By Heading Level

Another neat way to navigate the experience is by heading level. If you use the combination of your modifier keys + cmd + H you can traverse the document structure based on heading levels. You can also move back up the document by pressing shift in the sequence like so, modifier keys + shift + cmd + H.

Using the Heading Level Shortcut with VoiceOver History & Best Practices

Forms are one of the most powerful native elements we have in HTML. Whether you are searching for something on a page, submitting a form to purchase something or submit a survey. Forms are a cornerstone of the web, and were a catalyst that introduced interactivity to our experiences.

The history of the web form dates back to September 1995 when it was introduced in the HTML 2.0 spec. Some say the good ole days of the web, at least I say that. Stephanie Stimac wrote an awesome article on Smashing Magazine titled, “Standardizing Select And Beyond: The Past, Present And Future Of Native HTML Form Controls”.

In my opinion, the following are some best practices to follow when building an accessible form for the web.

  1. Labelling implicitly is 100% ok to do. Check out the https://www.w3.org/WAI/tutorials/forms/labels/ article. If the form author is unaware of the id, it can be labeled implicitly. I personally prefer the explicit way.
<!-- Implicit Label --> <label> First Name: <input type="text" name="firstname"> </label> <!-- Explicit Label --> <label for="name">Name:</label> <input type="text" id="name" name="name" required/>
  1. If a field is required in order for the form to be complete, use the required attribute. Be sure to have a visual indication that a field is required, too, because non-screenreader users need to know a field is required as well.
<input type="text" id="name" name="name" required />
  1. A button is used to invoke an action, like submitting a form. Use it! Don’t create buttons using div’s. A div is a container without semantic meaning by itself.
Demo Navigating a Web Form with VoiceOver

If you want to check out the code, navigate to the VoiceOver Demo GitHub repo. If you want to try out the demo above with your screen reader of choice, check out Navigating a Web Form with VoiceOver.

Screen Reader Software

Below is a list of various types of screen reader software you can use on your given operating system. If a Mac is not your machine of choice, there are options out there for Windows and Linux, as well as for Android devices.


NVDA is a screen reader from NV Access. It is currently only supported on PC’s running Microsoft Windows 7 SP1 and later. For more access, check out the NVDA version 2024.1 download page on the NV Access website!


“We need a better screen reader”

— Anonymous

If you understood the reference above, you are in good company. According to the JAWS website, this is what it is in a nutshell:

“JAWS, Job Access With Speech, is the world’s most popular screen reader, developed for computer users whose vision loss prevents them from seeing screen content or navigating with a mouse. JAWS provides speech and Braille output for the most popular computer applications on your PC. You will be able to navigate the Internet, write a document, read an email and create presentations from your office, remote desktop, or from home.”

JAWS website

Check out JAWS for yourself and if that solution fits your needs, definitely give it a shot!


Narrator is a built-in screen reader solution that ships with WIndows 11. If you choose to use this as your screen reader of choice, the link below is for support documentation on its usage.

Complete guide to Narrator


Orca is a screen reader that can be used on different Linux distributions running GNOME.

“Orca is a free, open source, flexible, and extensible screen reader that provides access to the graphical desktop via speech and refreshable braille.

Orca works with applications and toolkits that support the Assistive Technology Service Provider Interface (AT-SPI), which is the primary assistive technology infrastructure for Linux and Solaris. Applications and toolkits supporting the AT-SPI include the GNOME Gtk+ toolkit, the Java platform’s Swing toolkit, LibreOffice, Gecko, and WebKitGtk. AT-SPI support for the KDE Qt toolkit is being pursued.”

Orca Website TalkBack

Google TalkBack is the screen reader that is used on Android devices. For more information on turning it on and using it, check out this article on the Android Accessibility Support Site.

Browser Support

If you are looking for actual browser support for HTML elements and ARIA (Accessible Rich Internet Application) attributes, I suggest caniuse.com for HTML and Accessibility Support for ARIA to get the latest 4-1-1 on browser support. Remember, if the browser doesn’t support the tech, chances are the screen reader won’t either.

DigitalA11Y can help summarize browser and screen reader info with their article,  Screen Readers and Browsers! Which is the Best Combination for Accessibility Testing?


Thanks to Adrian Roselli, Karl Groves, Todd Libby, Scott O’Hara, Kev Bonnett, and others for clarifications and feedback!


Demystifying Screen Readers: Accessible Forms & Best Practices originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Managing User Focus with :focus-visible

Fri, 04/05/2024 - 12:13pm

This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out Accessible Forms with Pseudo Classes. In this post we are going to look at :focus-visible and how to use it in your web sites!

Focus Touchpoint

Before we move forward with :focus-visible, let’s revisit how :focus works in your CSS. Focus is the visual indicator that an element is being interacted with via keyboard, mouse, trackpad, or assistive technology. Certain elements are naturally interactive, like links, buttons, and form elements. We want to make sure that our users know where they are and the interactions they are making.

Remember don’t do this in your CSS!

:focus { outline: 0; } /*** OR ***/ :focus { outline: none; }

When you remove focus, you remove it for EVERYONE! We want to make sure that we are preserving the focus.

If for any reason you do need to remove the focus, make sure there is also fallback :focus styles for your users. That fallback can match your branding colors, but make sure those colors are also accessible. If marketing, design, or branding doesn’t like the default focus ring styles, then it is time to start having conversations and collaborate with them on the best way of adding it back in.

What is focus-visible?

The pseudo class, :focus-visible, is just like our default :focus pseudo class. It gives the user an indicator that something is being focused on the page. The way you write :focus-visible is cut and dry:

:focus-visible { /* ... */ }

When using :focus-visible with a specific element, the syntax looks something like this:

.your-element:focus-visible { /*...*/ }

The great thing about using :focus-visible is you can make your element stand out, bright and bold! No need to worry about it showing if the element is clicked/tapped. If you choose not to implement the class, the default will be the user agent focus ring which to some is undesirable.

Backstory of focus-visible

Before we had the :focus-visible, the user agent styling would apply :focus to most elements on the page; buttons, links, etc. It would apply an outline or “focus ring” to the focusable element. This was deemed to be ugly, most didn’t like the default focus ring the browser provided. As a result of the focus ring being unfavorable to look at, most authors removed it… without a fallback. Remember, when you remove :focus, it decreases usability and makes the experience inaccessible for keyboard users.

In the current state of the web, the browser no longer visibly indicates focus around various elements when they have focus. The browser instead uses varying heuristics to determine when it would help the user, providing a focus ring in return. According to Khan Academy, a heuristic is, “a technique that guides an algorithm to find good choices.”

What this means is that the browser can detect whether or not the user is interacting with the experience from a keyboard, mouse, or trackpad and based on that input type, it adds or removes the focus ring. The example in this post highlights the input interaction.

In the early days of :focus-visible we were using a polyfill to handle the focus ring created by Alice Boxhall and Brian Kardell, Mozilla also came out with their own pseudo class, :moz-focusring, before the official specification. If you want to learn more about the early days of the focus-ring, check out A11y Casts with Rob Dodson.

Focus Importance

There are plenty of reasons why focus is important in your application. For one, like I stated above, we as ambassadors of the web have to make sure we are providing the best, accessible experience we can. We don’t want any of our users guessing where they are while they are navigation through the experience.

One example that always comes to mind is the Two Blind Brothers website. If you go to the website and click/tap (this works on mobile), the closed eye in the bottom left corner, you will see the eye open and a simulation begins. Both the brothers, Bradford and Bryan Manning, were diagnosed at a young age with Stargardt’s Disease. Stargardt’s disease is a form of macular degeneration of the eye. Over time both brothers will be completely blind. Visit the site and click the eye to see how they see.

If you were in their shoes and you had to navigate through a page, you would want to make sure you knew exactly where you were throughout the whole experience. A focus ring gives you that power.


The demo below shows how :focus-visible works when added to your CSS. The first part of the video shows the experience when navigating through with a mouse the second shows navigating through with just my keyboard. I recorded myself as well to show that I did switch from using my mouse, to my keyboard.

Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.

The browser is predicting what to do with the focus ring based on my input (keyboard/mouse), and then adding a focus ring to those elements. In this case, when I am navigating through this example with the keyboard, everything receives focus. When using the mouse, only the input gets focus and the buttons don’t. If you remove :focus-visible, the browser will apply the default focus ring.

The code below is applying :focus-visible to the focusable elements.

:focus-visible { outline-color: black; font-size: 1.2em; font-family: serif; font-weight: bold; }

If you want to specify the label or the button to receive :focus-visible just prepend the class with input or button respectively.

button:focus-visible { outline-color: black; font-size: 1.2em; font-family: serif; font-weight: bold; } /*** OR ***/ input:focus-visible { outline-color: black; font-size: 1.2em; font-family: serif; font-weight: bold; } Support

If the browser does not support :focus-visible you can have a fall back in place to handle the interaction. The code below is from the MDN Playground. You can use the @supports at-rule or “feature query” to check support. One thing to keep in mind, the rule should be placed at the top of the code or nested inside another group at-rule.

<button class="button with-fallback" type="button">Button with fallback</button> <button class="button without-fallback" type="button">Button without fallback</button> .button { margin: 10px; border: 2px solid darkgray; border-radius: 4px; } .button:focus-visible { /* Draw the focus when :focus-visible is supported */ outline: 3px solid deepskyblue; outline-offset: 3px; } @supports not selector(:focus-visible) { .button.with-fallback:focus { /* Fallback for browsers without :focus-visible support */ outline: 3px solid deepskyblue; outline-offset: 3px; } } Further Accessibility Concerns

Accessibility concerns to keep in mind when building out your experience:

  • Make sure the colors you choose for your focus indicator, if at all, are still accessible according to the information documented in the WCAG 2.2 Non-text Contrast (Level AA)
  • Cognitive overload can cause a user distress. Make sure to keep styles on varying interactive elements consistent
Browser 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.

DesktopChromeFirefoxIEEdgeSafari864*No8615.4Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari12612712615.4 Links

Managing User Focus with :focus-visible originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

©2003 - Present Akamai Design & Development.