Modern CSS Features Most Developers Still Aren’t Using (2026)

Container queries, :has(), CSS nesting, subgrid, and oklch() color are baseline in every major browser by 2026, so no build step is needed. I reach for container queries first because components should respond to their own width, not the viewport.
Part of the complete web design guide.
On this page
- Container queries finally killed the “responsive component” problem
- :has() is the parent selector you gave up on
- Cascade layers make specificity fights a thing of the past
- Nesting is native now (and the syntax matters)
- What to actually do with this
- FAQ
- Do I still need Sass in 2026?
- Is :has() safe to use in production?
- What’s the minimum I should adopt if I only do one thing?
CSS has changed more in the last four years than in the previous fifteen. The features that used to require a preprocessor, a JavaScript listener, or a polyfill are now baseline in every major browser. If your stylesheets still look like 2018, you’re writing more code than you need to. Here’s what actually matters in 2026 and how to use it without breaking anything.
Container queries finally killed the “responsive component” problem
Media queries ask about the viewport. That was always the wrong question for a card, a sidebar widget, or anything reusable. A card doesn’t care how wide the screen is. It cares how wide its own container is. Container queries let you ask the right question.
.card-wrap {
container-type: inline-size;
container-name: card;
}
.card {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@container card (min-width: 30rem) {
.card {
grid-template-columns: 200px 1fr;
}
}
Drop that card in a narrow sidebar and it stacks. Drop the same markup in a wide main column and it goes horizontal. Same component, no JavaScript, no prop drilling a “isWide” boolean through your framework. Support is universal now. Every evergreen browser has shipped container queries for years, so the only reason not to use them is habit.
One gotcha: container-type: inline-size establishes containment, which means the element’s height no longer depends on its contents in the way you might expect. If you need height-based queries, use container-type: size and set an explicit height. Most people only need inline-size.
:has() is the parent selector you gave up on
For twenty years the answer to “can CSS select a parent based on its children” was no. Now it’s yes, and it’s been broadly supported since 2023.
/* Add spacing only when a form field has an error */
.field:has(.error) {
border-color: #c0392b;
background: #fdf2f0;
}
/* Style a card differently when it contains an image */
.card:has(img) {
padding-top: 0;
}
/* Disable the submit button's glow when the form is invalid */
form:has(:invalid) button[type="submit"] {
opacity: 0.5;
}
The practical win is state styling without JavaScript. An input with :user-invalid inside a :has() selector lets you react to real validation state, not just focus. You can also build a nav that highlights a section link when its target is scrolled into view, though that specific trick still needs scroll-driven animations to be fully reliable.
Performance note: :has() used to be expensive because it forced style recalculation up the tree. Modern engines have gotten much better at it, but if you’re applying :has() to a selector that matches thousands of nodes, measure before you ship. Profile in DevTools, don’t guess.
Cascade layers make specificity fights a thing of the past
This is the feature I’d push hardest on any team maintaining a large stylesheet. @layer lets you declare the order in which groups of styles win, independent of specificity. No more !important escalation.
@layer reset, base, components, utilities;
@layer base {
a { color: navy; }
}
@layer utilities {
.text-red { color: red; }
}
Because utilities is declared last in the layer order, .text-red beats the a selector even though a class and an element selector have different specificity. Layer order trumps specificity entirely. That means you can stop writing selectors like .nav .nav-list .nav-item a just to win a fight. You write .nav-item a and put it in the right layer.
If you’re working with a design system or a third-party CSS library, layers are how you keep your overrides sane. Import their stylesheet into its own layer, put your overrides in a later layer, and you never touch !important again.
Nesting is native now (and the syntax matters)
Native CSS nesting shipped everywhere in 2023. It’s not a Sass clone, and that trips people up.
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
}
& a {
text-decoration: none;
}
&:hover {
background: #eee;
}
}
The bare ul inside nav compiles to nav ul. The & refers to the parent. Where people get burned is assuming nesting changes specificity like Sass does with @extend or that you can nest a selector starting with a type name without it being interpreted as a descendant. Read the spec once and it clicks. Also worth knowing: nesting and :is() interact. A nested selector gets wrapped in :is() implicitly, which takes the highest specificity of its arguments. That can bite you if you’re not expecting it.
What to actually do with this
You don’t need to rewrite everything. Pick one and apply it where it hurts.
- If you’re fighting responsive components, start with container queries on your most reused card or widget.
- If you’re shipping form or state UI that leans on JavaScript for styling, replace one of those toggles with
:has(). - If your stylesheet has more than a handful of
!importantdeclarations, introduce layers before it gets worse.
I do a fair amount of Long Island web design work, and the pattern I see over and over is a site built five years ago where every new feature means another override tacked onto the end of a 4,000-line stylesheet. The fix isn’t a rewrite. It’s adopting two or three of these and letting the old code stay put until you touch it. CSS is backward compatible by design. You can move forward incrementally.
One last thing: check your build pipeline. If you’re running an older PostCSS setup or a Sass compiler that doesn’t pass through native nesting correctly, you can get double-compiled output that looks fine in dev and breaks in production. Test in a real browser, not just your bundler’s output.
FAQ
Do I still need Sass in 2026?
Less than you used to. Native nesting, custom properties, and color-mix() cover most of what people used Sass for. The main remaining reasons are mixins and functions, and even those have partial native answers now. If you’re starting fresh, plain CSS with a few PostCSS plugins is a reasonable default.
Is :has() safe to use in production?
Yes. It’s supported in all current evergreen browsers. The only real concern is performance on very large DOM trees, so profile if you’re applying it broadly. For typical component-level use, it’s fine.
What’s the minimum I should adopt if I only do one thing?
Container queries. They solve a problem media queries structurally cannot, they’re stable, and they make components genuinely portable between layouts. That’s the highest return for the least risk.


