If you have not been paying close attention to CSS over the past two years, you are in for a surprise. The language that many developers once dismissed as frustrating and limited has transformed into one of the most powerful styling tools in the history of the web. In 2024, CSS can do things that previously required JavaScript, preprocessors, or complex workarounds. Let us break down the most significant developments.

Container Queries — The End of the Viewport-Only Era

Container queries are arguably the most impactful CSS feature since flexbox. For over a decade, responsive design meant writing media queries based on the viewport width. This worked for page-level layouts but completely fell apart for reusable components. A card component that looked great in a wide sidebar broke when placed in a narrow one, because the component had no way to know how much space it actually had.

Container queries solve this by letting components respond to their container's size rather than the viewport. The syntax is clean and intuitive:

.card-container { container-type: inline-size; container-name: card; } @container card (min-width: 400px) { .card { display: grid; grid-template-columns: 200px 1fr; gap: 1rem; } } @container card (max-width: 399px) { .card { display: flex; flex-direction: column; } }

At StrikingWeb, we have been using container queries in production for the past several months. The impact on our component library has been dramatic. Components that previously needed three or four media query breakpoints with parent-class overrides now handle their own responsiveness automatically. Design system maintenance has become significantly easier.

Browser support is now universal across Chrome, Firefox, Safari, and Edge. There is no longer a reason to avoid container queries in production code.

The :has() Selector — CSS Gets a Parent Selector

Developers have been requesting a parent selector in CSS for as long as CSS has existed. The :has() pseudo-class finally delivers it, and it is even more powerful than what most developers expected.

The basic use case is selecting a parent based on its children:

/* Style a form group differently when it contains an invalid input */ .form-group:has(input:invalid) { border-color: var(--color-error); } /* Style a card differently when it has an image */ .card:has(img) { grid-template-rows: 200px 1fr; } /* Select a heading that is followed by a subtitle */ h2:has(+ .subtitle) { margin-bottom: 0.5rem; }

But :has() goes beyond parent selection. It can act as a conditional selector, enabling logic that previously required JavaScript:

/* Only show the clear button when the search input has content */ .search-wrapper:has(input:not(:placeholder-shown)) .clear-btn { display: block; } /* Change page layout when a modal is open */ body:has(.modal.active) { overflow: hidden; }

We have used :has() to replace dozens of lines of JavaScript in our projects. Form validation styling, conditional layouts, and interactive states that once required event listeners are now pure CSS.

Cascade Layers — Taming Specificity at Scale

Specificity conflicts have plagued CSS architecture for years. Cascade layers, defined with the @layer directive, provide a structured way to organize your CSS so that specificity battles become a thing of the past.

@layer reset, base, components, utilities; @layer reset { * { margin: 0; padding: 0; box-sizing: border-box; } } @layer base { h1 { font-size: 2.5rem; font-weight: 700; } p { line-height: 1.6; } } @layer components { .btn { padding: 0.75rem 1.5rem; border-radius: 0.5rem; } .card { padding: 1.5rem; border: 1px solid var(--border-color); } } @layer utilities { .mt-4 { margin-top: 1rem; } .hidden { display: none; } }

The order in which you declare layers determines their priority, regardless of the specificity of individual selectors within those layers. This means a simple class selector in the utilities layer will always override a complex selector chain in the components layer. This predictability is transformative for large codebases.

For projects that use third-party CSS alongside custom styles, cascade layers are especially valuable. You can wrap third-party styles in a low-priority layer, ensuring your custom styles always take precedence without resorting to !important.

CSS Nesting — Native Preprocessor Syntax

Native CSS nesting has reached full browser support in 2024, reducing the need for preprocessors like Sass for one of their most popular features:

.card { padding: 1.5rem; border-radius: 0.75rem; & .card-header { display: flex; align-items: center; gap: 0.5rem; } & .card-body { margin-top: 1rem; } &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } @media (min-width: 768px) { padding: 2rem; } }

The syntax is similar to Sass nesting but with one important difference: the & symbol is required when nesting selectors that start with a letter. This is a deliberate design choice that avoids ambiguity in the CSS parser. In practice, we recommend always using & for consistency.

View Transitions API

The View Transitions API brings smooth, animated transitions between page states — something that previously required complex JavaScript animation libraries. While not purely CSS, it works in tandem with CSS to create polished navigation experiences:

::view-transition-old(main-content) { animation: fade-out 0.3s ease-out; } ::view-transition-new(main-content) { animation: fade-in 0.3s ease-in; } .hero-image { view-transition-name: hero; }

Multi-page applications can now achieve the smooth transitions that were previously exclusive to single-page applications. We have started using view transitions on content-heavy sites where page navigation should feel seamless rather than jarring.

Other Notable Features

Subgrid

Subgrid allows child elements to align with the parent grid's tracks, solving the long-standing problem of aligning content across sibling grid items. This is essential for card layouts where titles, descriptions, and buttons need to line up across rows.

Color Functions and Color Spaces

CSS now supports the oklch() and oklab() color functions, which provide perceptually uniform color manipulation. The color-mix() function allows blending colors directly in CSS, reducing the need for preprocessor color functions.

Scroll-Driven Animations

Scroll-driven animations allow you to tie animation progress to scroll position without JavaScript. Progress bars, parallax effects, and reveal animations can now be pure CSS.

What This Means for Development Teams

The practical implications of these CSS advances are significant:

"Modern CSS has eliminated the need for roughly 30% of the JavaScript we used to write for UI interactions. That is a significant reduction in complexity and bundle size."

If your team is still writing CSS the way you did in 2020, it is time for a refresh. The language has evolved dramatically, and the teams that adopt these features will build faster, more maintainable, and more performant user interfaces.

Share: