specificity – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Tue, 17 Jan 2023 15:13:00 +0000 en-US hourly 1 https://wordpress.org/?v=6.2.2 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 specificity – CSS-Tricks https://css-tricks.com 32 32 45537868 :has is an unforgiving selector https://css-tricks.com/has-is-an-unforgiving-selector/ https://css-tricks.com/has-is-an-unforgiving-selector/#comments Wed, 11 Jan 2023 14:18:13 +0000 https://css-tricks.com/?p=376342 A little thing happened on the way to publishing the CSS :has() selector to the ol’ Almanac. I had originally described :has() as a “forgiving” selector, the idea being that anything in its argument is evaluated, even if one or …


:has is an unforgiving selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A little thing happened on the way to publishing the CSS :has() selector to the ol’ Almanac. I had originally described :has() as a “forgiving” selector, the idea being that anything in its argument is evaluated, even if one or more of the items is invalid.

/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }

See ::scoobydoo in there? That’s totally invalid. A forgiving selector list ignores that bogus selector and proceeds to evaluate the rest of the items as if it were written like this:

article:has(h2, ul) { }

:has() was indeed a forgiving selector in a previous draft dated May 7, 2022. But that changed after an issue was reported that the forgiving nature conflicts with jQuery when :has() contains a complex selector (e.g. header h2 + p). The W3C landed on a resolution to make :has() an “unforgiving” selector just a few weeks ago.

So, our previous example? The entire selector list is invalid because the bogus selector is invalid. But the other two forgiving selectors, :is() and :where(), are left unchanged.

There’s a bit of a workaround for this. Remember, :is() and :where()are forgiving, even if :has() is not. That means we can nest either of the those selectors in :has() to get more forgiving behavior:

article:has(:where(h2, ul, ::-scoobydoo)) { }

Which one you use might matter because the specificity of :is() is determined by the most specific item in its list. So, if you need to something less specific you’d do better reaching for :where() since it does not add to the specificity score.

/* Specificity: (0,0,1) */
article:has(:where(h2, ul, ::-scoobydoo)) { }

/* Specificity: (0,0,2) */
article:has(:is(h2, ul, ::-scoobydoo)) { }

We updated a few of our posts to reflect the latest info. I’m seeing plenty of others in the wild that need to be updated, so just a little PSA for anyone who needs to do the same.


:has is an unforgiving selector originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/has-is-an-unforgiving-selector/feed/ 4 376342
Taming the Cascade With BEM and Modern CSS Selectors https://css-tricks.com/taming-the-cascade-with-bem-and-modern-css-selectors/ https://css-tricks.com/taming-the-cascade-with-bem-and-modern-css-selectors/#comments Mon, 21 Nov 2022 13:59:15 +0000 https://css-tricks.com/?p=375144 BEM. Like seemingly all techniques in the world of front-end development, writing CSS in a BEM format can be polarizing. But it is – at least in my Twitter bubble – one of the better-liked CSS methodologies.

Personally, I think …


Taming the Cascade With BEM and Modern CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
BEM. Like seemingly all techniques in the world of front-end development, writing CSS in a BEM format can be polarizing. But it is – at least in my Twitter bubble – one of the better-liked CSS methodologies.

Personally, I think BEM is good, and I think you should use it. But I also get why you might not.

Regardless of your opinion on BEM, it offers several benefits, the biggest being that it helps avoid specificity clashes in the CSS Cascade. That’s because, if used properly, any selectors written in a BEM format should have the same specificity score (0,1,0). I’ve architected the CSS for plenty of large-scale websites over the years (think government, universities, and banks), and it’s on these larger projects where I’ve found that BEM really shines. Writing CSS is much more fun when you have confidence that the styles you’re writing or editing aren’t affecting some other part of the site.

There are actually exceptions where it is deemed totally acceptable to add specificity. For instance: the :hover and :focus pseudo classes. Those have a specificity score of 0,2,0. Another is pseudo elements — like ::before and ::after — which have a specificity score of 0,1,1. For the rest of this article though, let’s assume we don’t want any other specificity creep. 🤓

But I’m not really here to sell you on BEM. Instead, I want to talk about how we can use it alongside modern CSS selectors — think :is(), :has(), :where(), etc. — to gain even more control of the Cascade.

What’s this about modern CSS selectors?

The CSS Selectors Level 4 spec gives us some powerful new(ish) ways to select elements. Some of my favorites include :is(), :where(), and :not(), each of which is supported by all modern browsers and is safe to use on almost any project nowadays.

:is() and :where() are basically the same thing except for how they impact specificity. Specifically, :where() always has a specificity score of 0,0,0. Yep, even :where(button#widget.some-class) has no specificity. Meanwhile, the specificity of :is() is the element in its argument list with the highest specificity. So, already we have a Cascade-wrangling distinction between two modern selectors that we can work with.

The incredibly powerful :has() relational pseudo-class is also rapidly gaining browser support (and is the biggest new feature of CSS since Grid, in my humble opinion). However, at time of writing, browser support for :has() isn’t quite good enough for use in production just yet.

Lemme stick one of those pseudo-classes in my BEM and…

/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
  /* styles for all somethings, except for the special somethings */
}

Whoops! See that specificity score? Remember, with BEM we ideally want our selectors to all have a specificity score of 0,1,0. Why is 0,2,0 bad? Consider a similar example, expanded:

.something:not(a) {
  color: red;
}
.something--special {
  color: blue;
}

Even though the second selector is last in the source order, the first selector’s higher specificity (0,1,1) wins, and the color of .something--special elements will be set to red. That is, assuming your BEM is written properly and the selected element has both the .something base class and .something--special modifier class applied to it in the HTML.

Used carelessly, these pseudo-classes can impact the Cascade in unexpected ways. And it’s these sorts of inconsistencies that can create headaches down the line, especially on larger and more complex codebases.

Dang. So now what?

Remember what I was saying about :where() and the fact that its specificity is zero? We can use that to our advantage:

/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
  /* etc. */
}

The first part of this selector (.something) gets its usual specificity score of 0,1,0. But :where() — and everything inside it — has a specificity of 0, which does not increase the specificity of the selector any further.

:where() allows us to nest

Folks who don’t care as much as me about specificity (and that’s probably a lot of people, to be fair) have had it pretty good when it comes to nesting. With some carefree keyboard strokes, we may wind up with CSS like this (note that I’m using Sass for brevity):

.card { ... }

.card--featured {
  /* etc. */  
  .card__title { ... }
  .card__title { ... }
}

.card__title { ... }
.card__img { ... }

In this example, we have a .card component. When it’s a “featured” card (using the .card--featured class), the card’s title and image needs to be styled differently. But, as we now know, the code above results in a specificity score that is inconsistent with the rest of our system.

A die-hard specificity nerd might have done this instead:

.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }

That’s not so bad, right? Frankly, this is beautiful CSS.

There is a downside in the HTML though. Seasoned BEM authors are probably painfully aware of the clunky template logic that’s required to conditionally apply modifier classes to multiple elements. In this example, the HTML template needs to conditionally add the --featured modifier class to three elements (.card, .card__title, and .card__img) though probably even more in a real-world example. That’s a lot of if statements.

The :where() selector can help us write a lot less template logic — and fewer BEM classes to boot — without adding to the level of specificity.

.card { ... }
.card--featured { ... }

.card__title { ... }
:where(.card--featured) .card__title { ... }

.card__img { ... }
:where(.card--featured) .card__img { ... }

Here’s same thing but in Sass (note the trailing ampersands):

.card { ... }
.card--featured { ... }
.card__title { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}
.card__img { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}

Whether or not you should opt for this approach over applying modifier classes to the various child elements is a matter of personal preference. But at least :where() gives us the choice now!

What about non-BEM HTML?

We don’t live in a perfect world. Sometimes you need to deal with HTML that is outside of your control. For instance, a third-party script that injects HTML that you need to style. That markup often isn’t written with BEM class names. In some cases those styles don’t use classes at all but IDs!

Once again, :where() has our back. This solution is slightly hacky, as we need to reference the class of an element somewhere further up the DOM tree that we know exists.

/* ❌ specificity score: 1,0,0 */
#widget {
  /* etc. */
}

/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
  /* etc. */
}

Referencing a parent element feels a little risky and restrictive though. What if that parent class changes or isn’t there for some reason? A better (but perhaps equally hacky) solution would be to use :is() instead. Remember, the specificity of :is() is equal to the most specific selector in its selector list.

So, instead of referencing a class we know (or hope!) exists with :where(), as in the above example, we could reference a made up class and the <body> tag.

/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
  /* etc. */
}

The ever-present body will help us select our #widget element, and the presence of the .dummy-class class inside the same :is() gives the body selector the same specificity score as a class (0,1,0)… and the use of :where() ensures the selector doesn’t get any more specific than that.

That’s it!

That’s how we can leverage the modern specificity-managing features of the :is() and :where() pseudo-classes alongside the specificity collision prevention that we get when writing CSS in a BEM format. And in the not too distant future, once :has() gains Firefox support (it’s currently supported behind a flag at the time of writing) we’ll likely want to pair it with :where() to undo its specificity.

Whether you go all-in on BEM naming or not, I hope we can agree that having consistency in selector specificity is a good thing!


Taming the Cascade With BEM and Modern CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/taming-the-cascade-with-bem-and-modern-css-selectors/feed/ 21 375144
Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/ https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/#comments Wed, 24 Aug 2022 13:11:47 +0000 https://css-tricks.com/?p=372576 If a utility class only does one thing, chances are you don’t want it to be overridden by any styles coming from elsewhere. One approach is to use !important to be 100% certain the style will be applied, regardless of …


Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If a utility class only does one thing, chances are you don’t want it to be overridden by any styles coming from elsewhere. One approach is to use !important to be 100% certain the style will be applied, regardless of specificity conflicts.

The Tailwind config file has an !important option that will automatically add !important to every utility class. There’s nothing wrong with using !important this way, but nowadays there are better ways to handle specificity. Using CSS Cascade Layers we can avoid the heavy-handed approach of using !important.

Cascade layers allow us to group styles into “layers”. The precedence of a layer always beats the specificity of a selector. Specificity only matters inside each layer. Establishing a sensible layer order helps avoid styling conflicts and specificity wars. That’s what makes CSS Cascade Layers a great tool for managing custom styles alongside styles from third-party frameworks, like Tailwind.

A Tailwind source .css file usually starts something like this:

@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;

Let’s take a look at the official Tailwind docs about directives:

Directives are custom Tailwind-specific at-rules you can use in your CSS that offer special functionality for Tailwind CSS projects. Use the @tailwind directive to insert Tailwind’s base, components, utilities and variants styles into your CSS.

In the output CSS file that gets built, Tailwind’s CSS reset — known as Preflight — is included first as part of the base styles. The rest of base consists of CSS variables needed for Tailwind to work. components is a place for you to add your own custom classes. Any utility classes you’ve used in your markup will appear next. Variants are styles for things like hover and focus states and responsive styles, which will appear last in the generated CSS file.

The Tailwind @layer directive

Confusingly, Tailwind has its own @layer syntax. This article is about the CSS standard, but let’s take a quick look at the Tailwind version (which gets compiled away and doesn’t end up in the output CSS). The Tailwind @layer directive is a way to inject your own extra styles into a specified part of the output CSS file.

For example, to append your own styles to the base styles, you would do the following:

@layer base {
  h1 {
    font-size: 30px;
  }
}

The components layer is empty by default — it’s just a place to put your own classes. If you were doing things the Tailwind way, you’d probably use @apply (although the creator of Tailwind recently advised against it), but you can also write classes the regular way:

@layer components {
  .btn-blue {
    background-color: blue;
    color: white;
  }
}

The CSS standard is much more powerful. Let’s get back to that…

Using the CSS standard @layer

Here’s how we can rewrite this to use the CSS standard @layer:

@layer tailwind-base, my-custom-styles, tailwind-utilities;

@layer tailwind-base {
  @tailwind base;
}

@layer tailwind-utilities {
  @tailwind utilities;
  @tailwind variants;
} 

Unlike the Tailwind directive, these don’t get compiled away. They’re understood by the browser. In fact, DevTools in Edge, Chrome, Safari, and Firefox will even show you any layers you’ve defined.

CSS Cascade Layers with Tailwind CSS layers in DevTools.

You can have as many layers as you want — and name them whatever you want — but in this example, all my custom styles are in a single layer (my-custom-styles). The first line establishes the layer order:

@layer tailwind-base, my-custom-styles, tailwind-utilities;

This needs to be provided upfront. Be sure to include this line before any other code that uses @layer. The first layer in the list will be the least powerful, and the last layer in the list will be the most powerful. That means tailwind-base is the least powerful layer and any code in it will be overridden by all the subsequent layers. That also means tailwind-utilities will always trump any other styles — regardless of source order or specificity. (Utilities and variants could go in separate layers, but the maintainers of Tailwind will ensure variants always trump utilities, so long as you include the variants below the utilities directive.)

Anything that isn’t in a layer will override anything that is in a layer (with the one exception being styles that use !important). So, you could also opt to leave utilities and variants outside of any layer:

@layer tailwind-base, tailwind-components, my-custom-styles;

@layer tailwind-base {
  @tailwind base;
}

@layer tailwind-components {
  @tailwind components;
}

@tailwind utilities;
@tailwind variants;

What did this actually buy us? There are plenty of times when advanced CSS selectors come in pretty handy. Let’s create a version of :focus-within that only responds to keyboard focus rather than mouse clicks using the :has selector (which lands in Chrome 105). This will style a parent element when any of its children receive focus. Tailwind 3.1 introduced custom variants — e.g. <div class="[&:has(:focus-visible)]:outline-red-600"> — but sometimes it’s easier to just write CSS:

@layer tailwind-base, my-custom-styles;
@layer tailwind-base {
  @tailwind base;
}

@tailwind utilities;

@layer my-custom-styles {
  .radio-container {
    padding: 4px 24px;
    border: solid 2px rgb(230, 230, 230);
  }
  .radio-container:has(:focus-visible) {
    outline: solid 2px blue;
  }
}

Let’s say in just one instance we want to override the outline-color from blue to something else. Let’s say the element we’re working with has both the Tailwind class .outline-red-600 and our own .radio-container:has(:focus-visible) class:

<div class="outline-red-600 radio-container"> ... </div>

Which outline-color will win?

Ordinarily, the higher specificity of .radio-container:has(:focus-visible) would mean the Tailwind class has no effect — even if it’s lower in the source order. But, unlike the Tailwind @layer directive that relies on source order, the CSS standard @layer overrules specificity.

As a result, we can use complex selectors in our own custom styles but still override them with Tailwind’s utility classes when we need to — without having to resort to heavy-handed !important usage to get what we want.


Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/feed/ 2 372576
Manuel Matuzovic’s CSS Specificity Demo https://css-tricks.com/manuel-matuzovics-css-specificity-demo/ https://css-tricks.com/manuel-matuzovics-css-specificity-demo/#comments Fri, 25 Feb 2022 15:27:19 +0000 https://css-tricks.com/?p=364152 If you’re looking for a primer on CSS specificity, we’ve got that. And if you’re trying to get ahead of the game, you should be aware of CSS Cascade Layers as well.

One of the ways to help get …


Manuel Matuzovic’s CSS Specificity Demo originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If you’re looking for a primer on CSS specificity, we’ve got that. And if you’re trying to get ahead of the game, you should be aware of CSS Cascade Layers as well.

Screenshot of the CSS Specificity Demo.

One of the ways to help get a grasp of CSS specificity is thinking terms of “what beats what” or how strong the specificity is. Manuel Matuzovic has a helpful interactive step-by-step demo. You keep clicking the “Add selector” button, and the CSS shown (and applied to the page) changes with ever-increasingly-strong selectors applied to the body that change the background-color. At the end, it veers into not-really-selectors trickery, like using @keyframes to override things.

More specificity practice

If you enjoyed the trickery at the end, check out Francisco Dias’ A Specificity Battle!, an article we published a few years back that does a back-and-forth styling battle with nineteen steps “selecting” the same element to re-style it. CSS is cray sometimes.

To Shared LinkPermalink on CSS-Tricks


Manuel Matuzovic’s CSS Specificity Demo originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/manuel-matuzovics-css-specificity-demo/feed/ 1 364152
Don’t Fight the Cascade, Control It! https://css-tricks.com/dont-fight-the-cascade-control-it/ https://css-tricks.com/dont-fight-the-cascade-control-it/#comments Mon, 10 Jan 2022 15:22:08 +0000 https://css-tricks.com/?p=359886 If you’re disciplined and make use of the inheritance that the CSS cascade provides, you’ll end up writing less CSS. But because our styles often comes from all kinds of sources — and can be a pain to structure and …


Don’t Fight the Cascade, Control It! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If you’re disciplined and make use of the inheritance that the CSS cascade provides, you’ll end up writing less CSS. But because our styles often comes from all kinds of sources — and can be a pain to structure and maintain—the cascade can be a source of frustration, and the reason we end up with more CSS than necessary.

Some years ago, Harry Roberts came up with ITCSS and it’s a clever way of structuring CSS.

Mixed with BEM, ITCSS has become a popular way that people write and organize CSS.

However, even with ITCSS and BEM, there are still times where we still struggle with the cascade. For example, I’m sure you’ve had to @import external CSS components at a specific location to prevent breaking things, or reach for the dreaded !important at some point in time.

Recently, some new tools were added to our CSS toolbox, and they allow us to finally control the cascade. Let’s look at them.

O cascade, :where art thou?

Using the :where pseudo-selector allows us to remove specificity to “just after the user-agent default styles,” no matter where or when the CSS is loaded into the document. That means the specificity of the whole thing is literally zero — totally wiped out. This is handy for generic components, which we’ll look into in a moment.

First, imagine some generic <table> styles, using :where:

:where(table) {
  background-color: tan;
}

Now, if you add some other table styles before the :where selector, like this:

table {
  background-color: hotpink;
}

:where(table) {
  background-color: tan;
}

…the table background becomes hotpink, even though the table selector is specified before the :where selector in the cascade. That’s the beauty of :where, and why it’s already being used for CSS resets.

:where has a sibling, which has almost the exact opposite effect: the :is selector.

The specificity of the :is() pseudo-class is replaced by the specificity of its most specific argument. Thus, a selector written with :is() does not necessarily have equivalent specificity to the equivalent selector written without :is(). Selectors Level 4 specification

Expanding on our previous example:

:is(table) {
  --tbl-bgc: orange;
}
table {
  --tbl-bgc: tan;
}
:where(table) {
  --tbl-bgc: hotpink;
  background-color: var(--tbl-bgc);
}

The <table class="c-tbl"> background color will be tan because the specificity of :is is the same as table, but table is placed after.

However, if we were to change it to this:

:is(table, .c-tbl) {
  --tbl-bgc: orange;
}

…the background color will be orange, since :is has the weight of it’s heaviest selector, which is .c-tbl.

Example: A configurable table component

Now, let’s see how we can use :where in our components. We’ll be building a table component, starting with the HTML:

Let’s wrap .c-tbl in a :where-selector and, just for fun, add rounded corners to the table. That means we need border-collapse: separate, as we can’t use border-radius on table cells when the table is using border-collapse: collapse:

:where(.c-tbl) {
  border-collapse: separate;
  border-spacing: 0;
  table-layout: auto;
  width: 99.9%;
}

The cells use different styling for the <thead> and <tbody>-cells:

:where(.c-tbl thead th) {
  background-color: hsl(200, 60%, 40%);
  border-style: solid;
  border-block-start-width: 0;
  border-inline-end-width: 1px;
  border-block-end-width: 0;
  border-inline-start-width: 0;
  color: hsl(200, 60%, 99%);
  padding-block: 1.25ch;
  padding-inline: 2ch;
  text-transform: uppercase;
}
:where(.c-tbl tbody td) {
  background-color: #FFF;
  border-color: hsl(200, 60%, 80%);
  border-style: solid;
  border-block-start-width: 0;
  border-inline-end-width: 1px;
  border-block-end-width: 1px;
  border-inline-start-width: 0;
  padding-block: 1.25ch;
  padding-inline: 2ch;
}

And, because of our rounded corners and the missing border-collapse: collapse, we need to add some extra styles, specifically for the table borders and a hover state on the cells:

:where(.c-tbl tr td:first-of-type) {
  border-inline-start-width: 1px;
}
:where(.c-tbl tr th:last-of-type) {
  border-inline-color: hsl(200, 60%, 40%);
}
:where(.c-tbl tr th:first-of-type) {
  border-inline-start-color: hsl(200, 60%, 40%);
}
:where(.c-tbl thead th:first-of-type) {
  border-start-start-radius: 0.5rem;
}
:where(.c-tbl thead th:last-of-type) {
  border-start-end-radius: 0.5rem;
}
:where(.c-tbl tbody tr:last-of-type td:first-of-type) {
  border-end-start-radius: 0.5rem;
}
:where(.c-tbl tr:last-of-type td:last-of-type) {
  border-end-end-radius: 0.5rem;
}
/* hover */
@media (hover: hover) {
  :where(.c-tbl) tr:hover td {
    background-color: hsl(200, 60%, 95%);
  }
}

Now we can create variations of our table component by injecting other styles before or after our generic styles (courtesy of the specificity-stripping powers of :where), either by overwriting the .c-tbl element or by adding a BEM-style modifier-class (e.g. c-tbl--purple):

<table class="c-tbl c-tbl--purple">
.c-tbl--purple th {
  background-color: hsl(330, 50%, 40%)
}
.c-tbl--purple td {
  border-color: hsl(330, 40%, 80%);
}
.c-tbl--purple tr th:last-of-type {
  border-inline-color: hsl(330, 50%, 40%);
}
.c-tbl--purple tr th:first-of-type {
  border-inline-start-color: hsl(330, 50%, 40%);
}

Cool! But notice how we keep repeating colors? And what if we want to change the border-radius or the border-width? That would end up with a lot of repeated CSS.

Let’s move all of these to CSS custom properties and, while we’re at it, we can move all configurable properties to the top of the component’s “scope“ — which is the table element itself — so we can easily play around with them later.

CSS Custom Properties

I’m going to switch things up in the HTML and use a data-component attribute on the table element that can be targeted for styling.

<table data-component="table" id="table">

That data-component will hold the generic styles that we can use on any instance of the component, i.e. the styles the table needs no matter what color variation we apply. The styles for a specific table component instance will be contained in a regular class, using custom properties from the generic component.

[data-component="table"] {
  /* Styles needed for all table variations */
}
.c-tbl--purple {
  /* Styles for the purple variation */
}

If we place all the generic styles in a data-attribute, we can use whatever naming convention we want. This way, we don’t have to worry if your boss insists on naming the table’s classes something like .BIGCORP__TABLE, .table-component or something else.

In the generic component, each CSS property points to a custom property. Properties, that have to work on child-elements, like border-color, are specified at the root of the generic component:

:where([data-component="table"]) {
  /* These will will be used multiple times, and in other selectors */
  --tbl-hue: 200;
  --tbl-sat: 50%;
  --tbl-bdc: hsl(var(--tbl-hue), var(--tbl-sat), 80%);
}

/* Here, it's used on a child-node: */
:where([data-component="table"] td) {
  border-color: var(--tbl-bdc);
}

For other properties, decide whether it should have a static value, or be configurable with its own custom property. If you’re using custom properties, remember to define a default value that the table can fall back to in the event that a variation class is missing.

:where([data-component="table"]) {
  /* These are optional, with fallbacks */
  background-color: var(--tbl-bgc, transparent);
  border-collapse: var(--tbl-bdcl, separate);
}

If you’re wondering how I’m naming the custom properties, I’m using a component-prefix (e.g. --tbl) followed by an Emmett-abbreviation (e.g. -bgc). In this case, --tbl is the component-prefix, -bgc is the background color, and -bdcl is the border collapse. So, for example, --tbl-bgc is the table component’s background color. I only use this naming convention when working with component properties, as opposed to global properties which I tend to keep more general.

Now, if we open up DevTools, we can play around with the custom properties. For example, We can change --tbl-hue to a different hue value in the HSL color, set --tbl-bdrs: 0 to remove border-radius, and so on.

A :where CSS rule set showing the custom properties of the table showing how the cascade’s specificity scan be used in context.

When working with your own components, this is the point in time you’ll discover which parameters (i.e. the custom property values) the component needs to make things look just right.

We can also use custom properties to control column alignment and width:

:where[data-component="table"] tr > *:nth-of-type(1)) {
  text-align: var(--ca1, initial);
  width: var(--cw1, initial);
  /* repeat for column 2 and 3, or use a SCSS-loop ... */
}

In DevTools, select the table and add these to the element.styles selector:

element.style {
  --ca2: center; /* Align second column center */
  --ca3: right; /* Align third column right */
}

Now, let’s create our specific component styles, using a regular class, .c-tbl (which stands for “component-table” in BEM parlance). Let’s toss that class in the table markup.

<table class="c-tbl" data-component="table" id="table">

Now, let’s change the --tbl-hue value in the CSS just to see how this works before we start messing around with all of the property values:

.c-tbl {
  --tbl-hue: 330;
}

Notice, that we only need to update properties rather than writing entirely new CSS! Changing one little property updates the table’s color — no new classes or overriding properties lower in the cascade.

Notice how the border colors change as well. That’s because all the colors in the table inherit from the --tbl-hue variable

We can write a more complex selector, but still update a single property, to get something like zebra-striping:

.c-tbl tr:nth-child(even) td {
  --tbl-td-bgc: hsl(var(--tbl-hue), var(--tbl-sat), 95%);
}

And remember: It doesn’t matter where you load the class. Because our generic styles are using :where, the specificity is wiped out, and any custom styles for a specific variation will be applied no matter where they are used. That’s the beauty of using :where to take control of the cascade!

And best of all, we can create all kinds of table components from the generic styles with a few lines of CSS.

Purple table with zebra-striped columns
Light table with a “noinlineborder” parameter… which we’ll cover next

Adding parameters with another data-attribute

So far, so good! The generic table component is very simple. But what if it requires something more akin to real parameters? Perhaps for things like:

  • zebra-striped rows and columns
  • a sticky header and sticky column
  • hover-state options, such as hover row, hover cell, hover column

We could simply add BEM-style modifier classes, but we can actually accomplish it more efficiently by adding another data-attribute to the mix. Perhaps a data-param that holds the parameters like this:

<table data-component="table" data-param="zebrarow stickyrow">

Then, in our CSS, we can use an attribute-selector to match a whole word in a list of parameters. For example, zebra-striped rows:

[data-component="table"][data-param~="zebrarow"] tr:nth-child(even) td {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
}

Or zebra-striping columns:

[data-component="table"][data-param~="zebracol"] td:nth-of-type(odd) {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
}

Let’s go nuts and make both the table header and the first column sticky:


[data-component="table"][data-param~="stickycol"] thead tr th:first-child,[data-component="table"][data-param~="stickycol"] tbody tr td:first-child {
  --tbl-td-bgc: var(--tbl-zebra-bgc);
  inset-inline-start: 0;
  position: sticky;
}
[data-component="table"][data-param~="stickyrow"] thead th {
  inset-block-start: -1px;
  position: sticky;
}

Here’s a demo that allows you to change one parameter at a time:

The default light theme in the demo is this:

.c-tbl--light {
  --tbl-bdrs: 0;
  --tbl-sat: 15%;
  --tbl-th-bgc: #eee;
  --tbl-th-bdc: #eee;
  --tbl-th-c: #555;
  --tbl-th-tt: normal;
}

…where data-param is set to noinlineborder which corresponds to these styles:

[data-param~="noinlineborder"] thead tr > th {
  border-block-start-width: 0;
  border-inline-end-width: 0;
  border-block-end-width: var(--tbl-bdw);
  border-inline-start-width: 0;
}

I know my data-attribute way of styling and configuring generic components is very opinionated. That’s just how I roll, so please feel free to stick with whatever method you’re most comfortable working with, whether it’s a BEM modifier class or something else.

The bottom line is this: embrace :where and :is and the cascade-controlling powers they provide. And, if possible, construct the CSS in such a way that you wind up writing as little new CSS as possible when creating new component variations!

Cascade Layers

The last cascade-busting tool I want to look at is “Cascade Layers.” At the time of this writing, it’s an experimental feature defined in the CSS Cascading and Inheritance Level 5 specification that you can access in Safari or Chrome by enabling the #enable-cascade-layers flag.

Bramus Van Damme sums up the concept nicely:

The true power of Cascade Layers comes from its unique position in the Cascade: before Selector Specificity and Order Of Appearance. Because of that we don’t need to worry about the Selector Specificity of the CSS that is used in other Layers, nor about the order in which we load CSS into these Layers — something that will come in very handy for larger teams or when loading in third-party CSS.

Perhaps even nicer is his illustration showing where Cascade Layers fall in the cascade:

Credit: Bramus Van Damme

At the beginning of this article, I mentioned ITCSS — a way of taming the cascade by specifying the load-order of generic styles, components etc. Cascade Layers allow us to inject a stylesheet at a given location. So a simplified version of this structure in Cascade Layers looks like this:

@layer generic, components;

With this single line, we’ve decided the order of our layers. First come the generic styles, followed by the component-specific ones.

Let’s pretend that we’re loading our generic styles somewhere much later than our component styles:

@layer components {
  body {
    background-color: lightseagreen;
  }
}

/* MUCH, much later... */

@layer generic { 
  body {
    background-color: tomato;
  }
}

The background-color will be lightseagreen because our component styles layer is set after the generic styles layer. So, the styles in the components layer “win” even if they are written before the generic layer styles.

Again, just another tool for controlling how the CSS cascade applies styles, allowing us more flexibility to organize things logically rather than wrestling with specificity.

Now you’re in control!

The whole point here is that the CSS cascade is becoming a lot easier to wrangle, thanks to new features. We saw how the :where and :is pseudo-selectors allows us to control specificity, either by stripping out the specificity of an entire ruleset or taking on the specificity of the most specific argument, respectively. Then we used CSS Custom Properties to override styles without writing a new class to override another. From there, we took a slight detour down data-attribute lane to help us add more flexibility to create component variations merely by adding arguments to the HTML. And, finally, we poked at Cascade Layers which should prove handy for specifying the loading order or styles using @layer.

If you leave with only one takeaway from this article, I hope it’s that the CSS cascade is no longer the enemy it’s often made to be. We are gaining the tools to stop fighting it and start leaning into even more.


Header photo by Stephen Leonardi on Unsplash


Don’t Fight the Cascade, Control It! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/dont-fight-the-cascade-control-it/feed/ 4 359886
An Interview With Elad Shechter on “The New CSS Reset” https://css-tricks.com/an-interview-with-elad-shechter-on-the-new-css-reset/ https://css-tricks.com/an-interview-with-elad-shechter-on-the-new-css-reset/#comments Tue, 19 Oct 2021 14:48:40 +0000 https://css-tricks.com/?p=353462 Hey folks! Elad reached out to me to show me his new CSS reset project called the-new-css-reset. It’s quite interesting! I thought a neat way to share it with you is not only to point you toward it, but …


An Interview With Elad Shechter on “The New CSS Reset” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Hey folks! Elad reached out to me to show me his new CSS reset project called the-new-css-reset. It’s quite interesting! I thought a neat way to share it with you is not only to point you toward it, but to ask Elad some questions about it for your reading pleasure.

Here’s the entire code for the reset up front:

/*** The new CSS Reset - version 1.2.0 (last updated 23.7.2021) ***/

/* Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property */
*:where(:not(iframe, canvas, img, svg, video):not(svg *)) {
  all: unset;
  display: revert;
}

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

/*
  Remove list styles (bullets/numbers)
  in case you use it with normalize.css
*/
ol, ul {
  list-style: none;
}

/* For images to not be able to exceed their container */
img {
  max-width: 100%;
}

/* Removes spacing between cells in tables */
table {
  border-collapse: collapse;
}

/* Revert the 'white-space' property for textarea elements on Safari */
textarea {
  white-space: revert;
}

First, when talking about “CSS resets” we have two approaches:

  • Nicolas Gallagher’s Normalize.css is the gentle approach. Normalize.css is fixing differences between the implementation in different browsers.
  • Eric Meyer’s CSS Reset is the hard approach, saying that in most cases we don’t want basic styles from the browsers, like the font-size value we get from elements like <h1> through <h6>, or the default styles for the <ul> and <ol> list elements. For example, we use the list only for the semantic meaning, and because it helps in other ways for accessibility and SEO.

I love Normalize.css. I think it’s a must-have in any project, even if you prefer the CSS Reset idea.

And why is Normalize.css so important? Normalize.css touches shadow DOM elements that the CSS Reset doesn’t. When looking at Normalize.css, you will find special pseudo-classes like ::-moz-focus-inner, ::-webkit-file-upload-button, and more. It covers so many bases and that’s why I believe Normalize.css is a must-have in any project.

I love the hard CSS Reset as well. I think in most cases we don’t want the basic styles of the browser, and if we need it in a specific place, we will define it according to our need. This brings me to the point that I’m using both Normalize.css and CSS Reset combined. So, Normalize.css is first to load, followed by the hard CSS Reset.

So, why we need a new CSS reset? The CSS resets we have are built on old CSS features. But in the last several years, we’ve gotten new features built specifically for resetting things in CSS, and this got me thinking that now we can create a much more valid CSS reset using these new cutting-edge CSS features.

It seems to me the juiciest bit here is that very first ruleset. Let’s start with that first CSS property and value: all: unset;. That’s what is doing the heavy lifting in this CSS reset yes? How does that work?

all is the most exceptional CSS property because it allows us to reset all the properties that exist in the CSS all at once.

The property accepts several keywords. The two basics are initial and inherit; there are two smarter ones, which are unset and revert. To understand what all: unset does, we need to jump to the fundamental behavior of our CSS properties.

In CSS, we have two groups of properties:

  • Inherited properties group: These are properties that have inheritance by default — mainly typography properties.
  • Non-inherited properties group: These are all other properties that don’t inherit by default, for example, the Box Model properties that include padding, border, and margin.

Like typography properties, we want to keep the inherit behavior when we try to reset them. So, that’s where we’re able to use the inherit keyword value.

/* Will get values from the parent element value */
font-size: inherit;  
line-height: inherit;
color: inherit;

For the other properties in the non-inherited properties group, we want to get their initial value in most cases. It is worth mentioning that the initial keyword computes differently for different properties.

max-width: initial; /* = none */ 
width: initial; /* auto */
position: initial; /* = static */

After we understand the fundamentals as well as the inherit and initial keyword values, we understand that if we want to reset all of properties together, we can’t use them directly with the all property. That’s because, if we reset all of the properties to the initial value, i.e. all: initial, we lose the inherent behavior on the inherited properties group. And suppose we reset all properties with the inherit value. In that case, all the properties get an inheritance — even Box Model properties, which we want to avoid.

That’s why we have the unset value. unset resets the property according to its type. If we use it on an inherited property, it’s equal to inherit; if we use it on a natural non-inherited, it equals initial.

max-width: unset; /* = initial = none */
font-size: unset; /* = inherit = get parent element value */ 

This brings us back to the main feature of my CSS reset. What all: unset does is reset all the inherited properties to the inherit value, and all the other properties in the non-inherited properties group to their initial value.

This operation removes all the default user-agent-stylesheet styles that the browser is adding. To understand these substantial new CSS powers, all of this happened while I was doing only one operation to all HTML elements.

/* 
  Reset all: 
  - Inherited properties to inherit value
  - Non-inherited properties to initial value
*/
* {
  all: unset;
}

And then you follow it up with display: revert; — does all: unset; do things to the display property that would be undesirable?

Short answer: yes. The display property represents the basic structure which we do want to get from our user-agent stylesheet. As we saw in most of our properties, the unset value is doing an excellent job for us, and we reset all properties in one operation.

Now, to understand what the unique revert keyword value is doing for the display property, let’s talk about the two types of styles that we are getting from our browsers. The styles we are getting from our browsers are built from two layers:

  • Layer 1, the CSS initial value: As we already saw, the first layer is the initial values of all our properties in CSS, including the inherit behavior on some of the properties.
  • Layer 2, the user-agent stylesheet: These are the styles that the browser defines for specific HTML elements.

In most cases, when we want to reset things, we want to remove the basics styles of Layer 2. And when we do reset with all: unset, we remove all the styles of the user-agent stylesheet.

But the display property is exceptional. As we already saw, every property in CSS has only one initial value. This means that if we reset the display property to its initial, like on a <div> element or any other HTML element, it always returns the inline value.

Continuing with this logic, we connect the <div> element to the default display: block declaration, which we get from browsers. But we only get this behavior because of Layer 2, the user-agent stylesheet, which defines them. It’s built on the same idea that the font-size is bigger on heading elements, <h1> to <h6>, than any other HTML elements.

div { 
  display: unset; /* = inline */ 
}
span { 
  display: unset; /* = inline */ 
}
table { 
  display: unset; /* = inline */ 
}
/* or any other HTML element will get inline value */

This is, of course, unwanted behavior. The display property is the only exception we want to get from our browser. Because of that, I’m using the unique keyword value revert to bring back the default display value from the user-agent stylesheet..

The revert value is unique. First, it checks if there is a default style for the specific property in the user-agent stylesheet for the specific HTML element it is sitting on, and if it finds it, it takes it. If it doesn’t find it, revert works like the unset value, which means that if the property is an inherited property by default, it uses the inherit value; if not, it uses the initial value.

A diagram of all the CSS reset keywords

Then those two rules are within a ruleset with a selector where you select almost everything. It looks like you’re selecting everything on the page via the universal tag selector (*), but then removing a handful of things. Is that right? Why target those specific things?

When I started to imagine “The New CSS Reset” I didn’t think I would need exceptions. It was a lot more straightforward in my imagination.

But when I started to create experiences, I was replacing my old CSS reset with my new CSS reset (without all the exceptions), and I saw some things that broke my old projects, which I tested.

The main things that broke were elements that can get sizes via width and height attributes — elements like <iframe>, <canvas>, <img>, <svg>, and <video>. Unfortunately, when I reset everything, the width and height of those elements are defined by the auto value, which is stronger and removes the effect of the elements’ width and the height attributes.

This can be problematic because we want the exact size to come from the HTML element in cases where we add the dimensions via the HTML width and height attributes. We prefer to get it from the HTML, not from the CSS, because when it comes from the CSS, it can cause glitches when the page is loading.

The only way I found to remove the reset effect for all those particular elements is to put them under the :not() selector. In this case, my new CSS reset is harmful and not helpful, and because of that, I removed the effect for these specific elements.

Keeping specificity at a minimum seems important in a reset, so you don’t find yourself fighting the reset itself. Is that the idea behind :where()?

Yes, the idea of the :where() is to remove the specificity. We don’t need to describe more significant specificity in our CSS only to override the CSS reset.

In general, I think we will soon see a lot more cases of :where() wrapping things to remove their specificity, and not only to replace multiple selectors.

It looks like some extra special care for children of <svg> is in there. What is that about?

The second case, :not(svg *) is done with a separate :not() only because it is for a different issue. Touching the inner elements of an SVG can break the visual image, and this is one of those things that there isn’t any reasonable cause to interrupt the browser.

Let the image be an image. I say.

After the big resetting part, it goes into some bits that are more opinionated. For example, there are no browser disagreements about the initial value of box-sizing, but you’re changing it anyway. I’m a fan of that one myself, but I’m curious about the philosophy of what goes into a reset and what doesn’t.

In general, when it comes to a CSS reset, I think it is an opinion thing. For example, Eric Meyer’s CSS Reset chooses to remove the styles of specific things, and other things like the display property, are uninterrupted, which as you already saw, I totally agree with.

About box-sizing, yes, that is opinionated. I have been a web developer for 15 years. In that time, I’ve seen many web developers struggling to understand the default behavior of box-sizing, which I got so used to in the past. When there were talking about adding it to the CSS Reset many years ago, web developers, many of whom had been in the industry for a long time, were afraid of this change because, in general, people are scared of change.

But these days, I almost do not see any project that isn’t resetting all elements to box-sizing: border-box. A browser’s engines can’t fix the default awkward behavior of the default box-sizing: content-box, because if they do so, they will break support for older websites. But for newer projects, including this piece is a must since we’re left to solve it on our own.

And again, this is totally opinionated.

Two other rulesets, the removing of list styles and collapsing borders, are also in the Eric Meyer’s reset, so they have been around a long time! Starting with the list styles, I can see wanting to wipe those out as lists are often used for things that don’t need a marker, like navigation. But it feels a bit contentious these days, as list-style: none; wipes out the semantics of a list, as well on iOS. Any concerns there?

The short answer: no. No concerns on my end. Here’ why.

If we choose not to reset list-style, it means we can’t use list elements for navigation. This also means that we won’t get any semantics for any other browsers.

And now, if I need to choose between most browsers gaining these semantics, and no browsers gaining these semantics, I’m choosing the former, as more browsers gain from it than they lose.

Can you see yourself adding to this over time? Like if you find yourself doing the same things on projects over and over? Setting the max-width on images feels like that to me. Again, it’s not something browsers disagree on now, but also something that pretty much every project does.

Of course. If this reset is missing something that I didn’t consider, I will add it and release a new version. But it needs to be like your example of max-width where there is no good case where we want an image to overflow its container.

Have you seen this new Cascade Layers stuff? Any thoughts on how that might factor in to CSS resets down the road?

I didn’t think about it until you asked me. The Cascade Layers module is an exciting feature. It still doesn’t have any support, but most browser engines have already put this feature under a flag, and this means that there is a good chance that we will see this feature one year from now supported in all evergreen browsers.

For those who haven’t heard about Cascade Layers yet, the idea is that @layer can override styles without creating stronger specificity because every layer that loads after it is automatically stronger than the previous layers.

When this feature arrives, we will load the “reset” layers first. For example: first, Normalize.css, then the-new-css-reset, and then the @layer styles of the project.

@layer normalize; /* Create 1st layer named “normalize” */
@layer the-new-css-reset; /* Create 2nd layer named “the-new-css-reset” */
@layer project-styles; /* Create 3rd layer named “project-styles” */

This should make sure that the bottom layer always beats the top. This also means that removing specificity with :where(), like I did, will no longer be necessary.

@layer is one of the most exciting future features coming to CSS, thanks to Miriam Suzanne, who is doing, as always, a fantastic job.

Thanks for taking the time Elad!


An Interview With Elad Shechter on “The New CSS Reset” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/an-interview-with-elad-shechter-on-the-new-css-reset/feed/ 23 353462
Cascade Layers https://css-tricks.com/cascade-layers/ https://css-tricks.com/cascade-layers/#comments Tue, 21 Sep 2021 19:23:05 +0000 https://css-tricks.com/?p=352140 There is a new thing coming in CSS: @layer.

This comes from Miriam Suzanne, who is really on a tear with influencing important new CSS stuff. I’ve been hearing about all this, but then all a sudden it …


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

]]>
There is a new thing coming in CSS: @layer.

This comes from Miriam Suzanne, who is really on a tear with influencing important new CSS stuff. I’ve been hearing about all this, but then all a sudden it just dropped in experimental browsers.

Leave it to Bramus to really dig into all this with a fantastic post on it all:

With Cascade Layers coming, we developers will have more tools available to control the Cascade. The true power of Cascade Layers comes from its unique position in the Cascade: before Selector Specificity and Order Of Appearance. Because of that we don’t need to worry about the Selector Specificity of the CSS that is used in other Layers, nor about the order in which we load CSS into these Layers — something that will come in very handy for larger teams or when loading in third-party CSS.

Bramus Van Damme, “The Future of CSS: Cascade Layers (CSS @layer)

Emphasis mine.

That’s the rub here: this is a new thing that affects which selectors win. It’s going to require some re-wiring of our CSS brains, because layers is this entirely new (and powerful) part of determining what styles actually get applied.

I say powerful because a “higher” layer can literally beat a traditionally stronger selector even with a weaker selector in the layer.

/* First layer */
@layer base-layer {
  body#foo {
    background: tan;
  }
}
/* Higher layer, so this wins, despite selector strength */
@layer theme-layer {
  body.foo {
    background: #eee;
  }
}

/* Careful! Unlayered styles are more powerful than layers, even if the selector is weaker */
body {
  background: red;
}

Because that CSS at the bottom isn’t in a layer at all, it wins, even with the weaker selector

And you aren’t limited to one layer. You get to define them and use them however you want.

@layer reset;     /* Create 1st layer named “reset” */
@layer base;      /* Create 2nd layer named “base” */
@layer theme;     /* Create 3rd layer named “theme” */
@layer utilities; /* Create 4th layer named “utilities” */
/* Or, @layer reset, base, theme, utilities; */


@layer reset { /* Append to layer named “reset” */
  /* ... */
}

@layer theme { /* Append to layer named “theme” */
  /* ... */
}

@layer base { /* Append to layer named “base” */
  /* ... */
}

@layer theme { /* Append to layer named “theme” */
  /* ... */
}

Mind-blowing, really.

How are we going to use this?

I wonder if a common pattern might turn into…

  1. Layer everything, so priority levels are really clear. Maybe allow unlayered CSS for super powerful overrides only, but ideally even do that as a high-level layer.
  2. Resets as the lowest layer.
  3. Third-party stuff as the middle layer(s).
  4. Anything team-authored as the highest layer.

You won’t have to worry about leaving space in between (like you might with z-index) because you can adjust it without needing to attach numbers at any time.

Time shall tell.

Debugging

I hope DevTools expresses layers really clearly because there is going to be some serious head-scratching for a while when we see weaker-looking selectors winning because of layer placement.

Browser Support

Looks like caniuse is on the ball here!

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
9997No9915.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
11511511515.4

Updates

This stuff is super new (at the time of writing), so volatility is to be expected, I suppose. Looks like on October 6th, 2021 it was decided that unlayered styles are actually the strongest styles, not the weakest. I’ve attempted to update the article to show that.


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

]]>
https://css-tricks.com/cascade-layers/feed/ 20 352140
Using the Specificity of :where() as a CSS Reset https://css-tricks.com/using-the-specificity-of-where-as-a-css-reset/ https://css-tricks.com/using-the-specificity-of-where-as-a-css-reset/#comments Mon, 12 Jul 2021 14:30:34 +0000 https://css-tricks.com/?p=343613 I don’t know about you, but I write these three declarations many times in my CSS:

ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

You might yell at me and say I can just put those in my CSS …


Using the Specificity of :where() as a CSS Reset originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I don’t know about you, but I write these three declarations many times in my CSS:

ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

You might yell at me and say I can just put those in my CSS resets. I wish I could, but I don‘t want to and I’ll tell you why in a second.

User agents set values to those properties in a list for a purpose, and that is to make lists more readable. These are the default styles in chromium browsers for a <ul> element:

ul {
  list-style-type: disc;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
  padding-inline-start: 40px;
}

So, without adding any class in HTML or style in CSS, we get those for free. That‘s a nice thing and I don‘t want to lose it. But I would appreciate it if I could make the browser understand that there is very high chance I don’t want that default feature in cases where I add a class to the element.

So here is a quick solution to reset a <ul> element that has a class:

ul[class] {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

Now I don’t lose the default style except when I add a class to my <ul> element.

The problem

There is a problem with this solution. Imagine there is a list that we want to have a different list-style-type for it, like the following:

ul[class] {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

.list {
  list-style-type: square;
}

This doesn’t work since ul[class] has higher specificity. That’s where our solution breaks down.

We could add more weight to the selector’s specificity:

ul.list {
  list-style-type: square; /* Specificity: 0, 1, 1 */
}

/* or */

.sidebar .list {
  list-style-type: square; /* Specificity: 0, 2, 0 */
}

If you are OK adding more weight to the selector, you are good to go. But I’m not OK with it, personally. For example, I don’t want to put the element’s name in my CSS most of the times due to a separation of concerns principle. Or, if you are following BEM methodology, problems will most certainly arise as this conflicts with it.

So what can we do?

The solution

A few months ago, I learned about some hot selectors, including :is() and :where(). One thing about these two functional pseudo selectors, is that they can change specificity, giving us the power to nullify or increase that specificity.

The key about :where() is that it always has 0 specificity. So we can get rid of our problem very easily like this:

:where(ul[class]) {
  list-style: none;
}

.list {
  list-style: square; /* Now this works like a charm! */
}

With the power of this selector, libraries can give us style with no specificity. So there would be no specificity to compete with when we as authors write CSS.

Demo

In the following demo, you can remove :where() to see what we talked about in action:


Using the Specificity of :where() as a CSS Reset originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-the-specificity-of-where-as-a-css-reset/feed/ 6 343613
Should DevTools teach the CSS cascade? https://css-tricks.com/should-devtools-teach-the-css-cascade/ https://css-tricks.com/should-devtools-teach-the-css-cascade/#comments Fri, 21 May 2021 23:22:54 +0000 https://css-tricks.com/?p=340991 Stefan Judis, two days before I mouthed off about using (X, X, X, X) for talking about specificity, has a great blog post not only using that format, but advocating that browser DevTools should show us that value by …


Should DevTools teach the CSS cascade? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Stefan Judis, two days before I mouthed off about using (X, X, X, X) for talking about specificity, has a great blog post not only using that format, but advocating that browser DevTools should show us that value by selectors.

I think that the above additions could help to educate developers about CSS tremendously. The only downside I can think of is that additional information might overwhelm developers, but I would take that risk in favor of more people learning CSS properly.

I’d be for it. The crossed-off UI for the “losing” selectors is attempting to teach this, but without actually teaching it. I wouldn’t be that worried about the information being overwhelming. I think if they are considerate about the design, it can be done tastefully. DevTools is a very information-dense place anyway.

To Shared LinkPermalink on CSS-Tricks


Should DevTools teach the CSS cascade? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/should-devtools-teach-the-css-cascade/feed/ 2 340991
Hide Scrollbars During an Animation https://css-tricks.com/hide-scrollbars-during-an-animation/ https://css-tricks.com/hide-scrollbars-during-an-animation/#comments Wed, 24 Jun 2020 21:33:40 +0000 https://css-tricks.com/?p=313482 CSS still can’t animate to auto dimensions.

.dropdown {
  transition: 0.2s;
  height: 0;
}
.dropdown.open {
  /* the height will change, but it won't animate. */
  height: auto;
}

There is JavaScript trickery you can try. Brandon Smith outlined several


Hide Scrollbars During an Animation originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS still can’t animate to auto dimensions.

.dropdown {
  transition: 0.2s;
  height: 0;
}
.dropdown.open {
  /* the height will change, but it won't animate. */
  height: auto;
}

There is JavaScript trickery you can try. Brandon Smith outlined several techniques here a little while back. My mind always goes to this solution just because it’s so simple:

.dropdown {
  transition: 0.2s;
  max-height: 0;
}
.dropdown.open {
  /* 🎉 */
  max-height: 400px;
}

Now we have this 400px magic number which is really not ideal. But the fact that this works and is so simple makes it extremely appealing that I use it production all the time.

But the magic number isn’t the only problem. Another problem is scrollbars.

When we set max-height: 0;, we also need overflow: hidden; to make sure the dropdown is actually hidden when it is closed. When the dropdown is open, we should probably be using overflow: auto; so that we don’t accidentally cut off content in case the natural height of the dropdown is taller than the max-height after it expands. The use of overflow: auto; solves that problem while introducing another: during the expansion, our dropdown will always have scrollbars for at least part of the expansion, even if the final expansion height doesn’t need them. That’s awkward!

CSS trickery to the rescue.

We can still use overflow: auto; on the expanded state — we’ll just override it during the animation. As we learned in the great CSS specificity battle, @keyframes have an amazing ability to override anything while they are active. Let’s use them not to animate the opening, but just for this scrollbar-hiding functionality:

.dropdown {
  max-height: 0;
  overflow: hidden;
  transition: max-height 1.2s ease-in-out;
}
.dropdown.open {
  overflow: auto;
  max-height: 400px;
  animation: hide-scroll 1.2s backwards;
}
@keyframes hide-scroll {
  from, to { overflow: hidden; } 
}

That does the trick!

Try adjusting the height to something less to see how you don’t see scrollbars during the animation but only at the end when they are needed. That causes a little bit of jerkiness when the scrollbar pops in, but that was acceptable in my case as it’s rare that it happens at all. If you absolutely wanted to stop the jerkiness, you’d probably apply a (custom) scrollbar at all times to the dropdown and perhaps adjust the styling of the scrollbar during the animation, if needed.


Credit here to Mr. Stephen Shaw of the fancy @keyframers for this trick. I yanked him in to help me figure it out while I was working on it for something on CodePen. We decided to turn the trick into a video for the CodePen channel showcasing Collab Mode, which we used to figure out the problem/solution:


Hide Scrollbars During an Animation originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/hide-scrollbars-during-an-animation/feed/ 4 313482
“weeds of specificity” https://css-tricks.com/weeds-of-specificity/ Fri, 13 Mar 2020 20:19:58 +0000 https://css-tricks.com/?p=304890 Lara Schenck:

[…] with WordPress child themes, you are all but guaranteed to get into the weeds of specificity, hunting around theme stylesheets that you didn’t author, trying to figure out what existing declaration is preventing you from applying


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

]]>
Lara Schenck:

[…] with WordPress child themes, you are all but guaranteed to get into the weeds of specificity, hunting around theme stylesheets that you didn’t author, trying to figure out what existing declaration is preventing you from applying a new style, and then figuring out the least specificity you need to override it, and then thinking “Maybe it would be faster if I just wrote all of this myself”.

Her point wasn’t child themes (although I think that’s a perfect thing to point to as the way you work with them is all with overriding what is already there), but the expectation of knowledge:

[…] unless you are “a CSS person” this understanding of specificity and its impact on the future of the code-base is somewhat specialized knowledge. Should everyone who writes CSS be expected to understand these details? Maybe, but the more experienced I become in all kinds of development, I’m starting to think that’s an unrealistic expectation given how much other stuff we have to know as developers.

To Shared LinkPermalink on CSS-Tricks


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

]]>
304890
What’s the Difference Between Width/Height in CSS and Width/Height HTML attributes? https://css-tricks.com/whats-the-difference-between-width-height-in-css-and-width-height-html-attributes/ https://css-tricks.com/whats-the-difference-between-width-height-in-css-and-width-height-html-attributes/#comments Fri, 24 Jan 2020 16:03:15 +0000 https://css-tricks.com/?p=301933 Some HTML elements accept width and height as attributes. Some do not. Those attributes are sometimes referred to as presentational attributes. The thing to know about them is that they are overridden by any other styling information whatsoever. That makes them ideal as a fallback.


What’s the Difference Between Width/Height in CSS and Width/Height HTML attributes? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Some HTML elements accept width and height as attributes. Some do not. For example:

<!-- valid, works, is a good idea -->
<img width="500" height="400" src="..." alt="...">
<iframe width="600" height="400" src="..."></iframe>
<svg width="20" height="20"></svg>

<!-- not valid, doesn't work, not a good idea -->
<div width="40" height="40"></div>
<span width="100" height="10"></span>

Those attributes are sometimes referred to as presentational attributes. The thing to know about them is that they are overridden by any other styling information whatsoever. That makes them ideal as a fallback.

So, if CSS loads and has a declaration like:

img {
  width: 400px;
}

…that is going to override the width="500" on the <img> tag above. Presentational attributes are the weakest kind of styling, so they are overridden by any CSS, even selectors with very low specificity.

What might be a smidge confusing is that presentational attributes seem like they would have high specificity. These inline styles, for instance, are very strong:

<img style="width: 500px; height: 400px;" src="..." alt="...">

Using an inline style (which works on any element, not a select few), we’ve moved from the weakest way to apply width and height to one of the strongest. Regular CSS will not override this, with a selector of any specificity strength. If we need to override them from CSS, we’ll need !important rules.

img {
  width: 400px !important;
}

To reiterate, presentational attributes on elements that accept them (e.g. <img>, <iframe>, <canvas>, <svg>, <video>) are a good idea. They are fallback sizing and sizing information as the page is loading. They are particularly useful on <svg>, which may size themselves enormously in an awkward way if they have a viewBox and lack width and height attributes. Browsers even do special magic with images, where the width and height are used to reserve the correct aspect-ratio derived space in a situation with fluid images, which is great for a smooth page loading experience.

But presentational attributes are also weak and are usually overridden in the CSS.


What’s the Difference Between Width/Height in CSS and Width/Height HTML attributes? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/whats-the-difference-between-width-height-in-css-and-width-height-html-attributes/feed/ 3 301933
The Order of CSS Classes in HTML Doesn’t Matter https://css-tricks.com/the-order-of-css-classes-in-html-doesnt-matter/ https://css-tricks.com/the-order-of-css-classes-in-html-doesnt-matter/#comments Tue, 17 Dec 2019 15:04:07 +0000 https://css-tricks.com/?p=300141 That’s right! And I can prove it, too. Let’s look at some CSS first:

.a {
  color: red;
}

.b {
  color: blue;
}

And now let’s look at some markup:

<div class="a b">Here’s some text</div>

The text is going …


The Order of CSS Classes in HTML Doesn’t Matter originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
That’s right! And I can prove it, too. Let’s look at some CSS first:

.a {
  color: red;
}

.b {
  color: blue;
}

And now let’s look at some markup:

<div class="a b">Here’s some text</div>

The text is going to be blue because .b is defined last in the CSS, right? But what if we go about and switch the order in which those classes are called in HTML:

<div class="b a">Here’s some text</div>

What color do you think the text should be? Red or blue?

This certainly might sound like a silly question but it tends to trip up a lot of folks who happen to be familiar with CSS-in-JS solutions. And this week I’ve spoken to two very senior front end engineers who thought similarly as well!

But the text in the example above will always be blue no matter what order those CSS classes are in. And that’s because the markup is just reading the CSS in the order that it’s written — the cascade wins in this example.


The Order of CSS Classes in HTML Doesn’t Matter originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-order-of-css-classes-in-html-doesnt-matter/feed/ 8 300141
How much specificity do @rules have, like @keyframes and @media? https://css-tricks.com/how-much-specificity-do-rules-have-like-keyframes-and-media/ https://css-tricks.com/how-much-specificity-do-rules-have-like-keyframes-and-media/#comments Tue, 30 Jul 2019 22:58:11 +0000 https://css-tricks.com/?p=293075 I got this question the other day. My first thought is: weird question! Specificity is about selectors, and at-rules are not selectors, so… irrelevant?

To prove that, we can use the same selector inside and outside of an at-rule and …


How much specificity do @rules have, like @keyframes and @media? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I got this question the other day. My first thought is: weird question! Specificity is about selectors, and at-rules are not selectors, so… irrelevant?

To prove that, we can use the same selector inside and outside of an at-rule and see if it seems to affect specificity.

body {
  background: red;
}
@media (min-width: 1px) {
  body {
    background: black;
  }
}

The background is black. But… is that because the media query increases the specificity? Let’s switch them around.

@media (min-width: 1px) {
  body {
    background: black;
  }
}
body {
  background: red;
}

The background is red, so nope. The red background wins here just because it is later in the stylesheet. The media query does not affect specificity.

If it feels like selectors are increasing specificity and overriding other styles with the same selector, it’s likely just because it comes later in the stylesheet.

Still, the @keyframes in the original question got me thinking. Keyframes, of course, can influence styles. Not specificity, but it can feel like specificity if the styles end up overridden.

See this tiny example:

@keyframes winner {
  100% { background: green; }
}
body {
  background: red !important;
  animation: winner forwards;
}

You’d think the background would be red, especially with the !important rule there. (By the way, !important doesn’t affect specificity; it’s a per-rule thing.) It is red in Firefox, but it’s green in Chrome. So that’s a funky thing to watch out for. (It’s been a bug since at least 2014 according to Estelle Weyl.)


How much specificity do @rules have, like @keyframes and @media? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-much-specificity-do-rules-have-like-keyframes-and-media/feed/ 3 293075
Simplify Styling with Functional CSS https://css-tricks.com/simplify-styling-with-functional-css/ Wed, 07 Nov 2018 18:35:37 +0000 http://css-tricks.com/?p=278477 There is no doubt that “functional CSS” resonates strongly with some people. If that term is new to you, I belive it’s come to mean the same thing as “Atomic CSS” as defined by John Polacek here. Harry Nicholls …


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

]]>
There is no doubt that “functional CSS” resonates strongly with some people. If that term is new to you, I belive it’s come to mean the same thing as “Atomic CSS” as defined by John Polacek here. Harry Nicholls likens it to a function that can only produce one result (although I’d call that a pure function or pure component), but instead of a return value being entirely predictable based on inputs, it is an application of style that only does one thing.

I’m of two minds here. People say how fast they can work this way. Great! They like how predictable the applied styles are. Great! I can understand how a tiny stylesheet that doesn’t grow over time is appealing as well.

At the same time, I haven’t seen writing about other styling concerns. What happens with big redesigns? Is it about the same, time- and difficulty-wise, or do you spend more time tearing down all those classes? What happens when you need a style that isn’t available? Write your own? Or does that ruin the spirit of all this and put you in dangerous territory? How intense can all the class names get? I can think of areas I’ve styled that have three or more media queries that dramatically re-style an element. Putting all that information in HTML seems like it could get awfully messy. Is consistency harder or easier? I get that “p5” might be a useful way to apply an abstract amount of padding, but you still need to sprinkle it all over your codebase and know when to use it, right?

The closest I’ve been to being convinced about it was hearing from Adam just how configurable Tailwind is. In any case, I find this all endless fascinating and, if you love it, more power to ya.

To Shared LinkPermalink on CSS-Tricks


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

]]>
278477