HTML / CSS

Modern CSS That Replaces JavaScript: Container Queries, :has(), and More

CSS has shipped more meaningful features in the last two years than in the previous decade. Container queries, style queries, nesting, layers — these aren...

15 Oct 2023

Modern CSS That Replaces JavaScript: Container Queries, :has(), and More

A lot of the JavaScript in older codebases exists to work around things CSS could not do. That era is over. Layout that reacts to a container, styling a parent based on its children, multi-theme systems, even animated page transitions, all of it is now native CSS and widely supported across browsers. Here are the features that let you delete code, and how to use them.

Container queries: components that adapt to their context

Media queries respond to the viewport. That was always the wrong unit, a card does not care how wide the screen is, it cares how much room it was given. Container queries fix that.

Css
.card-list {
  container-type: inline-size;
}

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

The same card component now lays out one way in a narrow sidebar and another way in a wide main column, with no JavaScript measuring widths and no viewport breakpoints that break when you reuse the component somewhere new.

:has(), the parent selector we waited a decade for

:has() styles an element based on what it contains. This single selector erased a whole category of "add a class with JavaScript" code.

Css
/* A form field that has an invalid input */
.field:has(input:invalid) {
  border-color: red;
}

/* A card that contains an image gets a different layout */
.card:has(img) {
  grid-template-rows: 200px auto;
}

Previously you would run a script to detect the child and toggle a class on the parent. Now the stylesheet expresses it directly.

Cascade layers: ending specificity wars

@layer lets you declare the order of the cascade explicitly, so a utility no longer loses to some deeply nested selector by accident.

Css
@layer reset, base, components, utilities;

@layer components {
  .button { background: navy; }
}

@layer utilities {
  .bg-transparent { background: transparent; }
}

Layers declared later win, regardless of selector specificity. This is how you make a design system's utilities reliably override component styles without !important.

Native nesting

Nesting is now built in, no preprocessor required:

Css
.card {
  padding: 1rem;

  & .title {
    font-weight: 700;
  }

  &:hover {
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
  }
}

That removes one of the last big reasons teams reached for Sass.

View Transitions: animated changes without a library

The View Transitions API animates between two states, including across full page navigations, with the browser handling the crossfade and element morphing.

Css
@view-transition {
  navigation: auto;
}

.hero-image {
  view-transition-name: hero;
}

An element that carries the same view-transition-name on both pages animates smoothly from one position to the next. This used to require a client-side router and an animation library.

Smaller wins worth adopting

  • text-wrap: balance evens out headline line lengths, and pretty prevents typographic orphans.
  • color-mix() and oklch() let you compute and tune colors in CSS instead of precomputing palettes in JavaScript.
  • @property registers custom properties with a type so they can be animated.
  • aspect-ratio reserves layout space and kills a class of layout shift.

Wrapping up

The theme is the same across all of these: work the browser used to make you do in JavaScript is now declarative CSS. Container queries replace width-measuring scripts, :has() replaces class-toggling, cascade layers replace specificity hacks, and View Transitions replace animation libraries. Reach for the platform first, and a surprising amount of your UI code just disappears.

Go further
Live cohort on Maven

From Senior to Staff: Master the Architecture Skills That Get You Promoted

Go from shaky in design reviews to the engineer everyone trusts to architect the hard stuff.

View the live cohort

Keep reading