CSS

CSS in 2026: Modern Layout, Cascade Layers, and Container Queries

By Ken Key Jun 21, 2025 5 min read Updated Oct 16, 2026

CSS in 2026: Modern Layout, Cascade Layers, and Container Queries
The short answer

Modern CSS in 2026 comes down to three things: container queries that size components to their parent instead of the viewport, cascade layers that control specificity without !important fights, and :has() for parent selection. All are baseline in current browsers, so I use them by default.

Part of the complete web design guide.

On this page

Most CSS advice on the web is five years stale. Flexbox and Grid won, but the tools that actually changed how I write stylesheets are newer than that: container queries, cascade layers, and :has(). Here’s what I reach for in 2026 and why, with code you can paste into a project this afternoon.

Container queries replaced most of my media queries

A media query asks how wide the viewport is. A container query asks how wide the component’s parent is. That difference matters the moment you build a card that lives in a sidebar on one page and a three-column grid on another. With media queries you end up writing the same card twice and overriding it. With container queries the card styles itself.

.card-wrap {
 container-type: inline-size;
 container-name: card;
}

.card {
 display: grid;
 gap: 1rem;
}

@container card (min-width: 28rem) {
 .card {
 grid-template-columns: 12rem 1fr;
 align-items: start;
 }
}

Container queries are baseline across Chrome, Safari, Firefox, and Edge now, so there’s no reason to feature-detect them for normal sites. The one gotcha: container-type: inline-size creates containment, which means the element can’t size itself based on its children’s height in the way you might expect. If a layout breaks after you add it, that’s usually why.

Container query units are the underrated part

Alongside the queries you get cqw, cqh, cqi, and friends. Fluid type inside a component is now trivial without a single clamp() guess:

.card h3 {
 font-size: clamp(1.1rem, 4cqi, 1.6rem);
}

That heading scales to the card, not the screen. On a phone it stays readable. In a wide content column it gets bigger. Same component, no JavaScript.

Cascade layers fixed specificity wars for good

If you’ve ever fought a framework’s !important with your own !important, cascade layers are the fix. You declare an order once, and anything in a later layer beats anything in an earlier layer regardless of selector weight. Source order and specificity stop being the thing you argue about in code review.

@layer reset, base, components, utilities;

@layer base {
 a { color: #0b5fff; }
}

@layer utilities {
 .text-muted { color: #6b7280; }
}

Now .text-muted wins on any link it’s applied to, even though a and .text-muted have different specificity, because utilities come later. That’s the whole point. Put your reset first, your third-party CSS next, your components after that, and your utilities last. I’ve been doing this on client work for a while and it has removed an entire category of bug reports.

Where layers go wrong

  • Unlayered styles beat every layer. If half your CSS is in layers and half isn’t, the unlayered half wins, which is confusing until you know the rule.
  • Layers don’t cascade across separate stylesheets unless you import them into the same layer name. A <link> tag with no layer() is unlayered.
  • Don’t layer everything on day one. Start with reset, base, components, utilities and grow from there.

:has() is the selector that ended a lot of JavaScript

:has() lets you style a parent based on what’s inside it. It’s supported everywhere now, and it quietly deletes a lot of small scripts that used to toggle classes.

/* Add spacing only when a form field has an error message */
.field:has(.error) {
 border-color: #dc2626;
 padding-bottom: 1.5rem;
}

/* Style a nav item that contains the current page link */
.nav-item:has(a[aria-current="page"]) {
 font-weight: 600;
}

/* Disable a submit button's look when the form has an invalid field */
form:has(input:invalid) button[type="submit"] {
 opacity: .5;
}

That last one is the pattern I use most. It replaces a change handler on every input. The browser does the work, and it stays correct when fields are added or removed.

Subgrid finally makes card rows line up

You know the layout: three cards in a row, each with a title, body, and button, and the buttons never line up because the titles wrap differently. Subgrid solves it without fixed heights or flex hacks.

.card-grid {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
 gap: 1.5rem;
}

.card {
 display: grid;
 grid-row: span 3;
 grid-template-rows: subgrid;
}

.card .cta {
 align-self: end;
}

The card borrows the parent’s row tracks, so every title sits on row one, every body on row two, every button on row three. Buttons align across all cards no matter how long the copy is. This is the kind of thing we used to ship a layout library for.

What I’ve stopped doing

  • Float-based layouts. Obviously. But also clearing hacks and overflow: hidden as a clearfix.
  • Preprocessor nesting for its own sake. Native nesting works in all current browsers. I still use a build step for other reasons, but nesting isn’t one.
  • Pixel-based breakpoints everywhere. Container queries plus fluid units handle most responsive problems before a breakpoint is needed.
  • JavaScript for presentational state. If the browser can express it with :has() or a container query, it should.

How to adopt this without rewriting everything

You don’t need a big migration. Add a layer order at the top of your main stylesheet. Convert one card component to a container query and see how it behaves in a sidebar and a grid. Replace one toggle script with :has(). Each of those is maybe an hour of work and each one removes a class of bug rather than adding a feature.

If you’re working on a site and the CSS has become the reason changes take three times longer than they should, that’s usually a structural problem, not a skill problem. It’s the kind of thing I sort out for businesses around here, and you can read more about Long Island web design if that’s where you’re headed. Commack, Huntington, wherever, the fix is the same: get the cascade order right, then let the components own their own layout.

FAQ

Are container queries safe to use in production in 2026?

Yes. They’re supported in all current major browsers and have been for years. The only real caveat is the containment behavior of container-type, which can surprise you if you apply it to an element whose height depends on its children.

Do cascade layers replace BEM or other naming conventions?

No, they solve a different problem. Layers control which stylesheet rules win. Naming conventions control readability. You can use both, and plenty of teams do. Layers just mean you stop relying on selector weight to win fights.

Should I drop my preprocessor now that CSS has nesting and variables?

Only if the preprocessor was just doing nesting and variables. If you’re using it for loops, mixins, or a large shared design system, keep it. Native CSS has caught up on the basics but not on everything, and there’s no prize for removing a tool that’s still earning its place.

Have a project like this?

I do Long Island web design for Long Island businesses. If you need this done right, let’s talk.