A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.md 42KB

title: Write CSS. Not too much. Mostly scoped. url: https://www.leereamsnyder.com/write-css-not-too-much-mostly-scoped hash_url: 4e116948ed archive_date: 2024-03-23 og_image: https://www.leereamsnyder.com/og-image-generator/write-css-not-too-much-mostly-scoped/og-image.jpg description: Let’s talk about Tailwind and appease no one favicon: data:image/svg+xml,%3csvg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘64’%20height=‘64’%3e%3cstyle%3e%20@media%20(prefers-color-scheme:%20dark)%20{%20.lee%20{%20fill:%20%23FF4640;%20}%20.ream%20{%20fill:%20%23EC7F00;%20}%20.sny%20{%20fill:%20%23FFB258;%20}%20.der%20{%20fill:%20%23FFDFC6;%20}%20.letters%20{%20fill:%20%23373948;%20}%20}%20%3c/style%3e%3cpath%20class=‘lee’%20fill=‘%239B70FF’%20d=‘M0%203.066A3.066%203.066%200%200%201%203.066%200h57.868A3.066%203.066%200%200%201%2064%203.066v14.946H0V3.066Z’/%3e%3cpath%20class=‘ream’%20fill=‘%2300749D’%20d=‘M64%2034.108H0V15.713h64z’/%3e%3cpath%20class=‘sny’%20fill=‘%230092C5’%20d=‘M64%2031.808v20.695H0V31.808z’/%3e%3cpath%20class=‘der’%20fill=‘%238BCFFC’%20d=‘M64%2047.904v13.03A3.066%203.066%200%200%201%2060.934%2064H3.066A3.066%203.066%200%200%201%200%2060.934v-13.03h64Z’/%3e%3cg%20class=‘letters’%20fill=‘%23fff’%20fill-rule=‘nonzero’%3e%3cpath%20d=‘M13.275%2040.809c0-4.277%207.379-14.523%207.379-20.304%200-3.102-1.927-4.7-5.17-4.7-4.089%200-8.554%202.021-13.865%206.063a20.491%2020.491%200%200%201%202.726%204.277c3.807-3.055%206.016-4.559%207.849-4.559%201.081%200%201.457.517%201.457%201.363%200%203.76-6.956%2012.643-6.956%2019.458%200%204.136%202.444%206.063%207.144%206.063%205.687%200%2010.481-2.679%2016.215-9.494-1.175-.893-2.303-1.739-3.854-3.384-3.854%205.405-7.332%207.52-10.199%207.52-1.927%200-2.726-.846-2.726-2.303ZM50.286%2048c.33-1.692%201.128-3.431%201.833-5.17-.987.047-3.384.141-5.875.141-1.692%200-1.88-1.128-1.41-2.444%202.444-6.58%206.44-10.81%2010.575-10.81%201.363%200%202.491.423%203.243%201.41-.235-.047-.517-.047-.799-.047-2.538%200-3.9%201.739-3.9%203.76s1.268%203.29%203.336%203.29c3.29%200%204.935-3.431%204.935-7.332%200-3.619-1.692-6.627-5.687-6.627-4.183%200-7.52%203.055-10.763%209.87h-.423c.423-2.021.658-3.572.658-4.559%200-3.525-2.115-5.264-5.499-5.264-2.96%200-5.78%201.363-8.883%203.854.893.987%201.645%202.256%202.491%203.807%202.021-1.833%203.478-2.679%204.935-2.679%201.081%200%201.551.705%201.551%201.88%200%201.645-.94%205.311-2.726%209.776-.705%201.739-1.363%202.115-2.679%202.115-2.02%200-3.572-.141-4.324-.141-.329%201.692-1.08%203.431-1.786%205.17%202.632-.141%2018.33-.141%2021.197%200Z’/%3e%3c/g%3e%3c/svg%3e language: en_US

Table of contents

I’ve been a web developer of various stripes for over 15 years now. At roughly the same time that I was starting to sling HTML and CSS onto the internet, the author and journalist Michael Pollan attempted to answer the “supposedly incredibly complicated and confusing question of what we humans should eat in order to be maximally healthy”. He came up with some simple guidelines that could be boiled down to seven little words:

Eat food. Not too much. Mostly plants.

You should eat stuff that is recognizably food instead of hyper-processed garbage. Keep your portions reasonable. Prefer fruits and vegetables, which are overall better for you and the environment.

It was simple, sensible, and backed by science. With that, we never argued about what we should be putting in our bodies ever again.

Just kidding! Flash forward to today and we spend our time rehashing dietary trends from last century, arguing about water, urging people not to eat Tide Pods, and fighting over appropriate vessels to contain water.

Perhaps Pollan underestimated how much we like to argue online.

I see that same love of arguing playing out in web development with the endless discussion over Tailwind, the utility-first CSS framework and toolset that hit the scene in 2017.

If you’re somehow unfamiliar with Tailwind, the idea is the framework provides a gigantic pile of utility classes. Take some meat-and-potatoes markup like this (an abridged sample from their docs):

<div class=“chat-notification”>
  <div class=“chat-notification-content”>

<span class="hljs-tag">&lt;<span class="hljs-name">h4</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-notification-title"</span>&gt;</span>ChitChat<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>

</div> </div>

<style> .chat-notification {

<span class="hljs-attribute">display</span>: flex;
<span class="hljs-attribute">max-width</span>: <span class="hljs-number">24rem</span>;
<span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
<span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span>;
<span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0.5rem</span>;
<span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;
<span class="hljs-attribute">box-shadow</span>:
  <span class="hljs-number">0</span> <span class="hljs-number">20px</span> <span class="hljs-number">25px</span> -<span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>),
  <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> -<span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.04</span>);

}

.chat-notification-content {

<span class="hljs-attribute">margin-left</span>: <span class="hljs-number">1.5rem</span>;
<span class="hljs-attribute">padding-top</span>: <span class="hljs-number">0.25rem</span>;

}

.chat-notification-title {

<span class="hljs-attribute">color</span>: <span class="hljs-number">#1a202c</span>;
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.25rem</span>;
<span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.25</span>;

} </style>

With Tailwind, you bring in their huge stylesheet or (very preferably) use their build tooling and build the same component like so, using only the library’s classes that often apply a single styling property like font size or color or background color:

<div class=“p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4”>
  <div>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-xl font-medium text-black"</span>&gt;</span>ChitChat<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

</div> </div>

Take a beat and examine your feelings about those code samples. Do either of them raise your hackles or make you want to yell at strangers online?

You might think that using Tailwind and coding in this style would be a personal preference, but it clearly hits something deeper. Tailwind is possibly the most divisive thing I’ve seen in my career as a developer.

Here’s a small list of articles. First, some pro ones:

And some con ones:

Most recently, here’s What is Utility-First CSS? by Heydon Pickering, which is very spicy:

So why is this utility-first approach so popular at the moment? Partly because the designs we’re charged with coding often are f**ked and we need equally f**ked tools to wrangle them. Partly it’s because the f**ked tools we’ve adopted to write f**ked JavaScript don’t play so well with CSS or, for that matter, HTML. Mostly, it’s because developer insecurity and neophilia are easily exploited: “Have you ever written CSS you weren’t quite happy with? Well here’s a radical, paradigm-shifting, quasi-proprietary solution! You’ll never embarrass yourself again!”

It turns out, people in tech are particularly bad at distinguishing between paradigm shifts and paradigm sharts. That’s why we have nose-diving cryptocurrencies, dust-collecting monkey JPEG portfolios, and AI-generated children’s books teaching kids about pink, two-headed dinosaurs that never existed.

I mean c’mon that’s pretty funny.

We can’t stop talking about it. Here I am talking about it more.

I recommend that you read all of those articles, as both sides make plenty of interesting points. But in the interest of your time, if I had to greatly reduce their arguments, the Yay-Tailwind folks are saying:

  • Tailwind’s utility-first, just-use-these-classes philosophy clicks for me in a way that the many, many, many mental models for managing CSS haven’t.
  • Managing CSS at scale is very hard, and I mostly don’t have to do that with Tailwind.
  • Tailwind makes it easy to put together something that looks OK quickly. (This partially confuses the usefulness of a utility-first philosophy with the usefulness of a whole-ass design system, but, sure, OK. You like it.)

Meanwhile, the Down-With-Tailwind folks say:

  • You’re adding a dependency on Tailwind’s build tooling for customization and (especially) performance. Are you really looking for more complexity in your tech stack?
  • Because Tailwind is theoretically replacing all your CSS, their surface area is huge. If you’re going to learn a bunch of Tailwind to do box models, margin, padding, borders, typography, sizing, spacing, box-shadows, backgrounds, colors, fonts, animations, backdrop hue rotations(???) etc etc, why not just learn CSS?
  • If you are choosing to not learn CSS, you might regret that choice when you have to troubleshoot your Tailwind code, which is inevitably just CSS. (I personally don’t see this brought up often enough.)
  • The markup is ugly!

So, where do I stand?

I have built stuff with and without Tailwind professionally, and I see the merits of both sides.

The way Tailwind actively pushes against making hasty abstractions is — really — the smartest thing about it. In my experience, when you’re building something new you’re better off making something functional quickly and worrying about code elegance and deduplication and abstractions later, when you’re hopefully still in business. With a little practice, in a Tailwind project it’s relatively easy to get into a just-building-the-thing flow state. I get to shove the part of me that frets about good naming and specificity and leaking styles and efficient reuse into a drawer for a bit. It’s kinda nice.

And, sure, the output looks decent, if you accept that I will be able to spot your Tailwind-powered dashboard from a mile away just like the (approx.) 7 quadrillion Bootstrap-powered dashboards I’ve seen. (“You can customize it!” “You didn’t, though.”)

On the other hand, the pleasant-in-use simplicity of Tailwind falls apart when you’re doing something of even moderate complexity:

<button
  class="
  [&amp;>[data-slot=icon]]:-mx-0.5
  [&amp;>[data-slot=icon]]:my-0.5
  [&amp;>[data-slot=icon]]:shrink-0
  [&amp;>[data-slot=icon]]:size-5
  [&amp;>[data-slot=icon]]:sm:my-1
  [&amp;>[data-slot=icon]]:sm:size-4
  [&amp;>[data-slot=icon]]:text-[--btn-icon]
  [--btn-bg:theme(colors.zinc.900)]
  [--btn-border:theme(colors.zinc.950/90%)]
  [--btn-hover-overlay:theme(colors.white/10%)]
  [--btn-icon:theme(colors.zinc.400)]
  after:-z-10
  after:absolute
  after:data-[active]:bg-[--btn-hover-overlay]
  after:data-[disabled]:shadow-none
  after:data-[hover]:bg-[--btn-hover-overlay]
  after:inset-0
  after:rounded-[calc(theme(borderRadius.lg)-1px)]
  after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]
  before:-z-10
  before:absolute
  before:bg-[--btn-bg]
  before:data-[disabled]:shadow-none
  before:inset-0
  before:rounded-[calc(theme(borderRadius.lg)-1px)]
  before:shadow
  bg-[--btn-border]
  border
  border-transparent
  dark:[--btn-bg:theme(colors.zinc.600)]
  dark:[--btn-hover-overlay:theme(colors.white/5%)]
  dark:after:-inset-px
  dark:after:rounded-lg
  dark:before:hidden
  dark:bg-[--btn-bg]
  dark:border-white/5
  dark:text-white
  data-[active]:[--btn-icon:theme(colors.zinc.300)]
  data-[disabled]:opacity-50
  data-[focus]:outline
  data-[focus]:outline-2
  data-[focus]:outline-blue-500
  data-[focus]:outline-offset-2
  data-[hover]:[--btn-icon:theme(colors.zinc.300)]
  focus:outline-none
  font-semibold
  forced-colors:[--btn-icon:ButtonText]
  forced-colors:data-[hover]:[--btn-icon:ButtonText]
  gap-x-2
  inline-flex
  isolate
  items-center
  justify-center
  px-[calc(theme(spacing[3.5])-1px)]
  py-[calc(theme(spacing[2.5])-1px)]
  relative
  rounded-lg
  sm:px-[calc(theme(spacing.3)-1px)]
  sm:py-[calc(theme(spacing[1.5])-1px)]
  sm:text-sm/6
  text-base/6
  text-white"
>
  Button
</button>

That’s for one (1) button. The chat notification sample above didn’t make me retch, but this? 🤢

Ok, look, I’m not a purist about pretty code and one look at my website or wardrobe should tell you that I have questionable taste. But I would think that both sides of this debate would say that code looks absolutely bananas. But no, because that’s one of the buttons from the Catalyst framework, built by the Tailwind team.

Sure, fine, buttons can be deceptively complex and Catalyst is still in development, so maybe this changes in the future. But code like this — especially interactive elements or things that need media queries — is not atypical.

First, Tailwind’s build tooling lets you define new classes on the fly in HTML. This can be relatively harmless like defining a one-off margin length. Or it could be like above with, sm:py-[calc(theme(spacing[1.5])-1px)] where you’re involving media queries, accessing Tailwind’s theme values, then doing math to make a one-off length and OK now admit we’re just writing CSS but doing so very awkwardly. They’re committed to the bit, though, I’ll give them that. If you were hoping Tailwind would force your developers to stick to the design system, they won’t. (I didn’t.)

Second, Tailwind’s built-in solution to address the class landfill and compact your giant class heaps into something easier to consume (@apply) is both clearly not recommended and also a frankly weird mishmash of nonstandard CSS and Tailwind-ese. It reeks of “we added this to address complaints from the haterz, but only under duress.” I wouldn’t touch it.

So, yeah, Tailwind: it’s kinda pleasant, while occasionally being an extraordinary pain in the ass.

Meanwhile, in the past couple years since Tailwind rolled around, I’d say writing CSS is now also kinda pleasant, and while it also can occasionally be a pain in the ass, it’s far less frequent than it used to be.

So to tie things back to Michael Pollan, here’s where I land on the incredibly complicated and confusing question of how we humans should style documents and applications on the web at scale:

Write CSS. Not too much. Mostly scoped.

I’ll break that down.

For styling, you should strive to write code that is recognizably CSS.

You don’t need to know everything there is to know about CSS, but what you do learn will be portable and future-proof.

If you’re currently using Tailwind and like it, that’s really fine. I get it.

However, you might be kinda shocked at what CSS looks like these days.

For pete’s sake, centering things is easy. Has been for years.

You have multiple ways to equalize the heights of items in a row.

Elements that stay “stuck” to the viewport take two lines of code.

Hell, real soon now you won’t even need grid or flexbox to center things vertically.

Card demo authors rejoice, because you can respond to an element’s container, not just the viewport.

Really, skim through all of these modern solutions to old problems. I have personally had to work around literally all of them, so it struck a lot of nerves.

CSS variables, AKA custom properties, are universally supported. You can learn the basic syntax in like 5 minutes and learn some of the subtleties in under an hour and then you’re set for life.

They’re great for consuming design system tokens, and with inheritance and the cascade and the ability to use them in functions and shorthands you can really cut down on the amount of new CSS you have to write.

:root {
  --page-background-color: white;
  --text-color: black;
  --page-margin: 0.5rem;
}

/* adjust colors based on dark/light preference */
@media (prefers-color-scheme: dark) {
  :root {
    --page-background-color: black;
    --text-color: white;
  }
}

@media screen and (min-width: 50rem) {
  /* increased margins on wider screens */
  :root {
    --page-margin: 2rem;
  }
}

/* use the properties once! */
body {
  background-color: var(--page-background);
  color: var(--text-color);
  margin: var(--page-margin);
}

This is just scratching the surface. Check out Michelle Barker’s article for more uses for custom properties, other ideas from Ahmad Shadeed, or a whole pile of articles from Lea Verou.

For real, you can just write this in regular CSS and it works.

.body-copy {
  font-family: var(--font-family, serif);

  /* 
    equivalent to .body-copy > * + * 
  */
  > * + * {
    margin-block-start: 1em;
    max-width: 80ch;
  }
}

Length math methods like min(), max(), clamp() let you implement sophisticated constraints using just CSS. Check out how Utopia lets you build a fluid type and sizing scale with clamp() and zero media queries.

And, while we’re at it, it’s beyond safe to do some math in your stylesheets. The calc() function has been fully supported for years now.

Color, too, has gotten real neat in the past few years with new color spaces and the color-mix() function. Check out how Cory LaViska builds a button that will automatically compute reasonable hover and active colors from its current color in just a few lines of code.

Finally! You can say “style this, but only if it contains that” with :has() and it is glorious.

Check out how Stephanie Eckles builds a button component that can change its skin to accommodate text, icons, or any combination by checking its own internals with :has(). 👨‍🍳♥️

I have one major exception which I’ll get in to later.

But in the year two thousand and twenty four you maybe don’t need some of the classic make-CSS-better tooling anymore. Native support for nesting and variables probably covers most of what you’re doing with SASS or PostCSS, and browsers have moved past prefixed experimental properties, so you can drop Autoprefixer.

Finally, you might not need something to concatenate all your CSS files if you’re writing way less CSS in general. Speaking of…

People generally have problems with CSS when there’s a shitload of it. I like CSS, but I really do think the key to long-term happiness in a project is to avoid writing more of it — if you can.

Thankfully, new features and modern thinking about utilities and layout have you covered.

The best way I’ve found to avoid writing tons of CSS is to have a small arsenal of reusable, battle-hardened layout helpers at the ready.

Ideally you should not be writing more CSS when you have place some elements horizontally. You should instead have a utility class or a component that puts things side-by-side for you, and you should reach for that first.

The actual implementations will depend on your needs, your tooling, and probably on your design system. I have an article about thinking about layout in rows, stacks, and spaces that might illuminate things. Or look at Every Layout by Heydon Pickering and Andy Bell. Or SmolCSS by Stephanie Eckles for some other layout primitives.

Any system!

It’s very easy to rabbit-hole on this, but even a small selection of “approved” fonts, text sizes, spacing sizes, and colors will go a long way towards improving visual consistency and discouraging weird-looking one-off pages or components.

Slinging them around as CSS variables makes a ton of sense to me.

:root {
  /* system goes here */
  --heading-font-family: sans-serif;
  --heading-line-height: 1.1;
  --heading-color: green;
}

h1,
h2,
h3,
h4 {
  font-family: var(--heading-font-family);
  line-height: var(--heading-line-height);
  color: var(--heading-color);
}

Now, sticking to a system requires actual discipline from both designers and developers, but that’s a whole different problem.

Tailwind proponents do get this right, to a point: it is goddamned delightful to slap together a simple component or page with a few utility classes.

They’re undeniably handy, but don’t write one for every single CSS property.

Here’s my personal guidelines for when to write a utility class. First, is it some styling you’re writing all the time? Second, is the class name extremely obvious? If so, go for it. Otherwise, tread carefully.

As an example, you’re probably making components that have a wrapper element, but all that wrapper element is really doing is serving as a position: relative container for children.

<div class="some-dropdown-wrapper-with-relative-positioning">
  <div class="absolutely-positioned-dropdown"></div>
</div>

This is a spot where some single-property utility classes let you apply common properties quickly, spare you from having to think of a good class name for the wrapper, and the names are IMO obvious because they’re discrete keywords of a property:

<style>
  /* in your global stylesheet */
  .relative {
    position: relative;
  }
  .absolute {
    position: absolute;
  }
</style>

<div class="relative">
  <div class="absolute"></div>
</div>

I also have utilities for accessibility or layout annoyances that I end up using on every project ever, like these:

.nowrap,
.no-wrap /* lol I can never keep this straight */ {
  white-space: nowrap;
}

.screen-reader-only {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

Or properties derived from your design system, but now names are harder:

.font-primary {
  font-family: 'Heliotrope', Optima, sans-serif;
}
.font-monospace {
  font-family: 'Mono Lisa', monospace;
}

.fs-0 {
  font-size: var(--step-0);
}
.fs-1 {
  font-size: var(--step-1);
}
.fs-2 {
  font-size: var(--step-2);
}
/* and so on */

I do like to have resets like .p-0 or .m-0 to zero out padding and margin in a pinch, but not more than that. With layout utilities, I try to avoid thinking about padding and margin directly.

I get antsy writing utility classes for colors. Which color? Background? Text? Border? Outline? Are they also going to handle changes like hover, or dark mode? Should they? Are you going to account for tints and shades? Are there going to be so many that it’s hard to keep them straight? At that point, it’s easier to just write the CSS for whatever component I’m dealing with.

All this to say: keep the selection not-too-big: utility classes lose a lot of their power if you can’t keep most of the ones available to you in your noggin RAM.

But you would be stunned at how much you can build with a small handful of layout components and a couple of classes that let you access bits of your design system. That’s really the sweet spot you should be aiming for.

When one-offs or deviations from the system arise — which they absolutely will — there are a couple more tricks to keep new CSS to a minimum…

All browsers now support the :is and :where pseudo-classes.

Both are great for writing less verbose CSS. The classic example is combining :hover/:focus selectors into single selectors like so:

a {
  color: purple;
}

a:is(:hover, :focus) {
  color: blue;
}

The zero specificity of :where() is amazing for keeping the specificity of your selectors as low as possible, which makes overrides — should you need them — a lot easier. But if you’re having specificity problems, you might want to look at scoping.

It’s not your enemy! Learn what properties inherit, and set them as high as you possibly can.

One classic example is font-family. If you’ve got a primary font, set that on the body and everything will inherit it, and you don’t write font-family ever again.

body {
  font-family: sans-serif;
}

/* ahem these don't inherit by default */
button,
input,
select,
textarea {
  font: inherit;
}

You might not have to think of more classes if you’re adding additional markup for accessibility. You can latch on to those attributes that you’re adding anyway. Adrian Roselli has a big roundup of ideas.

/* good if you're doing this anyway */
textarea[aria-invalid='true'] {
  --border-color: red;
}

If you’re up to speed with modern CSS and writing way less of it and you’re still struggling, I’m willing to bet that your issue is managing the scope or reach of your selectors.

Maybe your selectors are targeting more elements than you intended, or you have conflicts when you combine components. This is where you really start fighting with specificity and selector ordering and get to thinking that CSS is stupid as hell.

Scoping your CSS more-or-less solves all these problems for you, and modern browsers and tooling make this increasingly accessible.

Although I mentioned above that you can live without a lot of CSS tooling, if you can take advantage of something that automatically writes styles scoped to a component’s markup, do it.

Because these work by slightly altering and (typically) increasing the specificity of your selectors, there’s sometimes subtleties and gotchas, but these things help you sidestep so many other CSS authoring issues that they’re absolutely worth it: they let you write simple selectors with very low specificity, they keep your styles from “leaking” out of the component, and they’re easier to work with than all the subtleties of the Shadow DOM.

The lightest possible thing I know of is the WebC template language and its webc:scoped mechanism. The Svelte framework also has built-in, default support for scoped styles, as does Vue.

If you’re using React or some other web framework that forgot that styles are a thing, CSS-in-JS libraries like styled-components or emotion generally output scoped styles.

Or look into CSS modules, where you import .css files in your scripts. It’s not a standard, but lots of build pipelines and frameworks let you do it.

What if I told you that CSS now has a native, globally supported way to reduce specificity and effectively reorder entire blocks of code or imported files? Meet cascade layers. These still scramble my brain a little bit, but theme and library authors in particular should be looking real hard at this right now. Shipped in all browsers two years ago.

If you’re working in web components aka custom elements, the Shadow DOM lets you write styles that are scoped to the component’s generated, isolated markup. This is a whole thing and comes with a whole heap of caveats, but it’s standardized and available without extra tooling. This introduction to the shadow DOM is pretty solid. If you’re really interested in web components, Thomas Wilburn’s book about them is the best practical overview I’ve read.

This is Chromium-only right now, but @scope lets you write boundaries for selectors, either in code or based on the parent of the style tag. Ollie Williams and Keith J. Grant have good introductions here.

If you’re not ready for things like cascade layers or you can’t adopt additional tooling, the battle-proven mental frameworks like BEM are and were popular for a reason. You’ve gotta get everyone on board and (ugh) think of names for things, but the broad guidelines are sound: writing modular code with low specificity class selectors can definitely help you avoid big swaths of CSS-at-scale problems.

Maybe do your best to keep the class names shortish, though, mmm-kay? 😉

So, yeah, I’ve been at this for a while, and this is the answer: Write CSS. Not too much. Mostly scoped.

I’m sure we’ll never argue about how to manage CSS ever again.