What are you trying? Are you happy with it?
(as a point of reference)
Your mileage will vary
Doesn’t mean we all need to

I think they are all pretty neat and I see the point…

But I’m a bunch of years into this and still generally… write the selector I need and then style with that.
Conventions won’t save us
Conventions can develop over time

YUI, Blueprint, 960gs, etc…
<div class=" column col6of12 last " >

CSS frameworks decrease the maintainability of code.
(this was before RWD & Media Queries)
(which may provide sizing)
(the variable axis for blocks)


A CSS “object” is a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript.
Avoid even unique component styles
(Unique components, without unique CSS)
widthheightSMACSS, BEM, ITCSS, CUBE, etc…
Nearly every convention since is built on the ideas Natalie & Nicole presented 16 years ago.
Work from broad defaults at the start, towards narrow overrides at the end.

From p {} through .text-center {}






Macro Before Micro

Most of the work is already done for you with global and high-level styles.

Where these other approaches lose me is when they push away from using the built-in features of the language. Some of them getting more and more extreme about re-inventing solutions that already exist!
It’s not just CSS, HTML also has built-in functionality that we should avoid duplicating.

I decided to stop using the word semantics. Instead I talk about the UX of HTML…

Instead of explaining the meaning of a certain element, I show them what it does.
/* doesn't have to be fancy */
- .btn { … }
+ button { … }
There’s been a decades-long trend to decouple styles from functionality, but I agree with Craig Abbott that flexibility comes at a cost.
We’re giving developers too many options to break a system that we should be helping them maintain.
.btn » button.btn-submit » button[type=submit].link » a:any-link.hidden » [hidden].active » [aria-current]aria-current
Without intervention by the author, [the
pselector] already knows how to identify paragraphs.

Building with accessible semantics from the get-go can give you expressive, meaningful style hooks for free.
/* expressive semantics */
form:has(:invalid) [type="submit"] { … }
/* some name we came up with? */
.form__btn--submit--invalid { … }
a[href*="://"] { /* external links */ }
a[href^="https:"] { /* secure links */ }
a[href$=".pdf"] { /* pdf links */ }
button[aria-pressed=true] { /* pressed buttons */ }
img:not([alt]) { /* images without alt text */ }
article & section Elements
It’s a practical assumption that more targeted styles should override less targeted styles. That’s usually true!
h2:has(> button[aria-expanded="false"]) + div {
display: none;
}
And I think it’s something we should lean into rather than avoiding. Selectors are primarily for selecting, and they’re meant to be expressive.
.block .element.modifier { /* 3 */ }
.block__element--modifier { /* 1 */ }
Removing specificity is often accomplished by avoiding semantics, and re-naming everything in one place – where we’re most likely to have name conflicts.
Then getting mad about the results, and using importance to blow up the whole thing.
*types.classes & [attributes]#IDsWe don’t need to limit ourselves in this weird way. Classes are not the only option, and we’re allowed to write full sentences.
.card.info { border-color: teal; } /* ✅ */
.card--warning { border-color: orange; } /* ❌ */
.card { border-color: gray; }
(still meaningful and expressive)
Still, there are times when specificity falls short, and we need an escape hatch. That’s when we should consider using scope to target selectors more narrowly, or layers to clarify selector priorities.

Scopes help us organize by component, while layers help us organize by purpose. So a single file component will likely describe a single scope, while it might include several layers that are shared across multiple components.
I know browser support for scope isn’t ready for most people, but it will be soon. We’ll talk more about progressive enhancements, but in the meantime there are a number of scoping tools that can help.

On the other end we can use layers to start segmenting our code, so there’s less potential for specificity issues.




<template>…</template>
<style>
@layer components {
/* … */
}
</style>
@layer reset, default, theme, patterns, layout, content;
Layering everything is good, because it keeps you flexible for adding new layers when and where you need. But generally I prefer large buckets over managing a long list of special layers.
<feature> With <tool>??
Can it handle colors and lengths?
(otherwise ‘tools’ become obstacles)
<div class="md:flex">
<div class="md:flex-shrink-0">
<img class="rounded-lg md:w-56" src="..." alt="">
</div>
<div class="mt-4 md:mt-0 md:ml-6">
<div class="uppercase tracking-wide text-sm text-indigo-600 font-bold">...</div>
</div>
</div>
¯\_(ツ)_/¯
When you… reduce the amount of time you spend writing and editing CSS… you must instead spend more time changing HTML classes…


We’ve started to refer to the OddBird approach as ‘Poetic CSS’ – and I’m currently releasing a course about it. The first module is available, and more are coming soon. We’ll provide a discount for everyone here if you’re interested.
That’s a play on poetiCss, for any philosophy nerds

Say exactly what you mean using efficient and expressive language that has a broad reach and room for interpretation.
I’d summarize it something like this.
CSS provides a lot of expressive features – various selectors, specificity, layers, queries, units, inheritance, custom properties, and so on. These are all useful tools!
Like poetry, it helps to have a wide vocabulary, that is used efficiently.
The goal of a selector is to select things
Heuristics are useful
@layer for layering
Based on purpose
@scope for scoping
Based on context
@scope (prose-block) to ([data-scope]) {
p { … }
ul { … }
blockquote { … }
}
a {
&:any-link { … }
&:focus, &:hover, &:active { … }
&[aria-current] { … }
&.call-to-action { … }
}
.card.big { /* classes */ }
.card--big { /* BEM */ }
/* Custom element with attributes */
my-card[data-size="big"] { /* … */ }
.Card { /* … */ }
my-card { /* … */ }



Styles should be reusable…

a { text-decoration: var(--link-decoration, underline); }
nav { --link-decoration: none; }
h2, h3, h4, h5, h6 {
margin-block-start: 1lh;
&:first-child {
margin-block-start: unset;
}
}
h2, h3, h4, h5, h6 {
&:not(:first-child) {
margin-block-start: 1lh;
}
}
:not() to exclude selectors[hidden] { display: none !important; }
[visually-hidden]:not(:focus-within) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
<!-- Element-attached/inline -->
<div style="--ease: var(--in-out-back);">
[style*='--ease'] {
--in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--in-out-back: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

CSS is unlike anything else… designed for the realities of a flexible, multilingual, multi-device web.