New Dec 2, 2024

Chris Corner: Approaching CSS

Company/Startup Blogs All from CodePen View Chris Corner: Approaching CSS on blog.codepen.io

We’ve been using Cascade Layers in our CSS here at CodePen quite a bit lately. A pretty clear non-complicated use case is that we tend to apply one class to stuff. That includes a “library” component. So imagine the prototypical Button, which would have a .button selector applying styling to it. But this library component also accepts a class so that usage of the component can apply it’s own styles if needed.

Like this:

<button class="library-button usage-class-button">

Since we’re building React components, it expresses like:

<Button className={classNames(styles.root, props.className)}>

So now we’ve got two selectors that are exactly the same in specificity. Which one wins? It will come down to source order. And to be quite honest, I’m not really sure how that will play out in every single scenario the component could be used on CodePen. The styles might be bundled, sometimes by different build processes, and placed into combined CSS files or even sometimes loaded dynamically. That has led to the occasional situation where we artificially increased the specificity of the class selector we’re passing in just so it for sure wins. .myClass.myClass, for instance. That’s dumb.

Instead, we can force our library components to have intentionally less powerful styles via Cascade Layers. So our “root” style can be like:

@layer library {
  .root {

  }
}

Alone, that works. That means the .myClass stuff will always win, as unlayered styles are always stronger than layered styles. But to keep us honest we have setup the order such that we can slot things in in an expected way going forward:

@layer library, root, page, component;

That’s in our global CSS such that we have those levels to poke in at, when we want to use layering but at a specific level of strength.

Layers is a nice enough API that we can also do sub-layering. Meaning rather than just @layer library { } wrapping all library components, we’d actually do something like this for Button:

@layer library.Button {

}

That means it’s still slotted in at the library level, but if we needed to at some point, we could declare the order of components such that one could beat another. We haven’t needed it yet, but it feels like the right move.

I can absolutely see how people can think Cascade Layers are useless. (Manuel Matuzović doesn’t actually think this, but he makes some points with code on how people could.) They didn’t strike me as terrible useful right away as something you could “sprinkle in” to a project until I came across out own use case here. Mostly I thought of it as “put Bootstrap in a layer, unlayer your own styles” as the #1 use case. But now I’m starting to think “keep specifically generally flat, and when they are conflicts use layers instead of increased specificity” as a pretty big use case as well.


Let’s do some bonus CSS links because we can:

Scroll to top