CSS has shipped more useful features in the last two years than in the previous decade combined. The problem is most developers are still writing CSS like it is 2020. Container queries, native nesting, the :has() selector - these are not experimental anymore. They are in every major browser, production-ready, and they solve real problems you are currently working around with JavaScript or convoluted workarounds.
Here are the features worth adopting today, with practical examples for each.
Container Queries
Media queries respond to the viewport. Container queries respond to the size of a parent element. This is the difference between page-level responsiveness and component-level responsiveness, and it changes how you think about building reusable components.
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
gap: 1rem;
}
}Now your card component adapts based on where it is placed - sidebar, main content, modal - without you writing separate media queries for each context. Browser support is at 97% globally. There is no reason to wait on this one.
The :has() Selector
Developers asked for a parent selector for over a decade. Now we have one, and it is more powerful than what most people imagined.
/* Style a card differently if it contains an image */
.card:has(img) {
grid-template-rows: 200px auto;
}
/* Disable submit when form has invalid inputs */
form:has(:invalid) button[type="submit"] {
opacity: 0.4;
pointer-events: none;
}
/* Style the body when a dialog is open */
body:has(dialog[open]) {
overflow: hidden;
}:has() replaces a ton of JavaScript that was only there to add or remove classes based on DOM state. Forms, navigation menus, toggle states - you can handle all of it in CSS now. At 95% browser support, it is ready.
CSS Nesting
Native CSS nesting landed in all browsers in 2024. If you have been using Sass primarily for nesting, you might not need a preprocessor anymore.
.card {
padding: 1rem;
border-radius: 8px;
h2 {
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
p {
color: #666;
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@media (min-width: 768px) {
padding: 2rem;
}
}The syntax is close to Sass but not identical. The & is optional for element selectors in modern implementations but still required for pseudo-classes and pseudo-elements. Media queries can be nested directly inside rule blocks, which keeps related styles together instead of scattered across the file.
color-mix()
Generating color variations used to require preprocessor functions or JavaScript. color-mix() does it natively in CSS.
:root {
--brand: #4f8fff;
}
.button {
background: var(--brand);
}
.button:hover {
background: color-mix(in oklch, var(--brand), white 20%);
}
.button:active {
background: color-mix(in oklch, var(--brand), black 20%);
}Mix any two colors in any color space. The oklch space gives perceptually uniform results, so your hover states look natural. No more manually calculating lighter and darker variants of your brand colors. Define one color variable and derive everything else from it.
Scroll-Driven Animations
Scroll-based animations previously required Intersection Observer or scroll event listeners in JavaScript. The new scroll-driven animations API does it purely in CSS.
.fade-in {
animation: fadeIn linear;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}The element fades in as it enters the viewport, driven entirely by scroll position. No JavaScript, no libraries, no intersection observers. You can also create scroll progress indicators, parallax effects, and sticky header animations. Browser support is around 85% and climbing, so use it with a fallback for now.
View Transitions API
Smooth page transitions used to require single-page application frameworks. The View Transitions API brings native animated transitions to multi-page sites.
@view-transition {
navigation: auto;
}
::view-transition-old(root) {
animation: fade-out 0.2s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}For same-document transitions, wrap your DOM changes in document.startViewTransition(). The browser automatically cross-fades between states. For cross-document transitions (MPA), the CSS-only approach above works. Give specific elements a view-transition-name and the browser will animate them individually between pages.
Support is in Chrome, Edge, and Safari. Firefox is implementing it. This is one to start experimenting with now, even if you add it as a progressive enhancement.
What You Should Do
Pick one feature from this list and use it in your current project. Container queries and :has() have the broadest support and the most immediate impact. CSS nesting reduces file complexity. color-mix() simplifies design systems. Scroll-driven animations and view transitions are progressive enhancements that degrade gracefully.
The CSS platform is moving fast. The gap between what is possible and what most developers are using has never been wider. Close that gap and your code gets simpler, your JavaScript bundles get smaller, and your sites get faster.