CSS

Modern CSS in 2026: What Still Works, What to Stop Writing

By Ken Key Feb 25, 2025 5 min read Updated Oct 16, 2026

Modern CSS in 2026: What Still Works, What to Stop Writing
The short answer

In 2026, write cascade layers, container queries, :has(), nesting, and logical properties. Stop writing !important, float layouts, viewport media queries for components, vendor prefixes, and huge resets. The cascade is now manageable, so specificity hacks and clearfixes are dead weight.

Part of the complete web design guide.

On this page

CSS has changed more in the last four years than in the previous fifteen. Most of the tutorials still ranking on page one were written before flexbox had good browser support, and a lot of what they teach will make your stylesheets harder to maintain in 2026. Here is what actually matters now, and what you should stop writing.

The cascade is a feature, not a bug

For years the standard advice was “use the lowest specificity you can get away with, then add !important when you can’t.” That advice existed because we had no real tool for managing the cascade. Now we do. Cascade layers let you declare an explicit order of precedence, and specificity stops being a war you fight with selectors.

@layer reset, base, components, utilities;

@layer base {
 a { color: #0645ad; }
}

@layer components {
 .card a { color: inherit; }
}

In that example, the .card a rule wins even though it has higher specificity, because the components layer is declared after base. That is the point: order of layers beats specificity, so you can write the selector that makes sense instead of the selector that wins. For any project with more than one person touching the CSS, layers are the single biggest quality-of-life improvement available.

Container queries replaced most of your media queries

Media queries ask “how wide is the viewport?” Container queries ask “how wide is the thing this component lives inside?” That second question is almost always the one you actually care about. A card in a three-column grid and the same card in a sidebar should look different, and container queries handle that without a single viewport breakpoint.

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

@container card (min-width: 30rem) {
 .card {
 display: grid;
 grid-template-columns: 8rem 1fr;
 gap: 1.5rem;
 }
}

You still need a handful of media queries for page-level layout, print styles, and prefers-reduced-motion. But component-level responsiveness belongs in container queries now. This is also the reason design systems finally work: a component can be dropped anywhere and adapt on its own.

:has() is the parent selector you waited twenty years for

It shipped in every major browser back in 2023, and it is fully safe to use in production in 2026. If you are still adding a JavaScript class to a form field so you can style its label, stop.

/* Style the label only when its input has content */
.field:has(input:not(:placeholder-shown)) label {
 transform: translateY(-1.25rem);
 font-size: 0.75rem;
}

/* Remove margin from a heading that directly follows an image */
img + h2:has(+ p) {
 margin-block-start: 0.5rem;
}

The performance concern people raise about :has() is real but narrow. It is fast when the browser can scope the match. It gets slow when you write something like body:has(.modal) and force a check against the whole document on every mutation. Keep :has() close to the element it affects and you will never notice it.

Native nesting: mind the ampersand

Nesting is baseline now, no preprocessor required. The one gotcha that trips people up is that nested element selectors behave differently than you might expect, because the browser treats them like :is().

.nav {
 display: flex;
 gap: 1rem;

 & a {
 text-decoration: none;
 }

 /* This is WRONG if you meant "direct child of .nav" */
 .item { padding: 0.5rem; }
}

That last rule compiles to .nav .item, which is fine. But if you write & > .item you get exactly what you asked for. And if you nest a bare element selector you get :is(.nav) a, which has the specificity of the most specific part of the :is() list, not the least. That catches people coming from Sass.

Stop reaching for JavaScript for layout

A short list of things people still do in JS that CSS handles natively:

  • Equal-height columns: use display: grid or align-items: stretch.
  • Sticky headers: position: sticky has been solid for years.
  • Truncating text: text-overflow: ellipsis for one line, line-clamp for several.
  • Smooth scrolling: scroll-behavior: smooth, plus a prefers-reduced-motion guard.
  • Animating on scroll: animation-timeline: view() is shipping broadly now, though check your support targets before betting a landing page on it.

The modern reset is about five lines

The 200-line resets you see copy-pasted everywhere are mostly obsolete. Browsers have converged on sane defaults. What you actually want in 2026:

*, *::before, *::after { box-sizing: border-box; }

body {
 margin: 0;
 line-height: 1.5;
 -webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg {
 display: block;
 max-width: 100%;
}

input, button, textarea, select {
 font: inherit;
}

That is it. Everything else is opinion.

Architecture matters more than syntax

I build and maintain sites for businesses around here, and the CSS problems I get called in to fix are almost never about a missing feature. They are about a stylesheet that grew to 8,000 lines with no order to it, where nobody knows which rule is winning. A quick note on how I structure that work lives on my Long Island web design page, but the short version is: layers first, then a small set of tokens, then components that own their own styles and expose variants through custom properties.

:root {
 --space-sm: 0.5rem;
 --space-md: 1rem;
 --space-lg: 2rem;
 --radius: 6px;
}

.button {
 padding: var(--space-sm) var(--space-md);
 border-radius: var(--radius);
 background: var(--btn-bg, #1a1a1a);
 color: var(--btn-fg, #fff);
}

.button--ghost {
 --btn-bg: transparent;
 --btn-fg: #1a1a1a;
 border: 1px solid currentColor;
}

Custom properties as the variant API is the pattern I keep coming back to. It is less code than modifier classes that redeclare every property, and the theme is overridable from anywhere without touching the component file.

What to stop doing

  • Stop using !important to win a specificity fight. Fix the layer order instead.
  • Stop writing float-based grids. They were a workaround for a browser that no longer exists.
  • Stop importing a full utility framework when you need twelve utilities. Write them.
  • Stop assuming a preprocessor is required. PostCSS is still useful for autoprefixing and minification, but nesting and variables are native now.
  • Stop testing only in Chrome. Safari and Firefox both ship these features, but the edge cases differ, especially around :has() and container query units.

FAQ

Do I still need a CSS preprocessor in 2026?

For most projects, no. Nesting, variables, and math are native. Reach for one only if you depend on its specific features, like mixins with complex logic or a large existing codebase built around it.

Is :has() safe to use in production now?

Yes. It has been in all major browsers since 2023. Just keep it scoped to a small subtree rather than the whole document, and you will avoid the performance pitfalls.

Should I use container queries instead of media queries everywhere?

No. Use container queries for components and media queries for page-level layout, print output, and user preference checks like reduced motion or dark mode.

Have a project like this?

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