Modern CSS Features in 2025: Transforming How You Style
Explore cutting-edge CSS features like container queries, cascade layers, and has() selector that are revolutionizing how we build responsive, maintainable stylesheets.
Modern CSS Features in 2025: Transforming How You Style
CSS has evolved dramatically in recent years. New features are making stylesheets more powerful, maintainable, and expressive than ever before. Let's explore the game-changers that should be in every developer's toolkit.
Container Queries
Finally! Responsive design based on parent container size, not viewport:
Before: Media Queries
/* Based on viewport width */
@media (min-width: 768px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Problem: Cards look the same regardless of where they're placed.
After: Container Queries
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.card {
display: block;
}
@container sidebar (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Result: Cards adapt to their container, not the viewport.
Real-World Example
.product-grid {
container-type: inline-size;
}
.product-card {
/* Mobile layout by default */
display: flex;
flex-direction: column;
}
/* When container has space, switch to horizontal */
@container (min-width: 400px) {
.product-card {
flex-direction: row;
}
}
/* When container is large, add more columns */
@container (min-width: 800px) {
.product-card {
grid-column: span 2;
}
}
The :has() Selector
CSS finally has a parent selector:
Select Parent Based on Children
/* Card with an image */
.card:has(img) {
display: grid;
grid-template-columns: 1fr 2fr;
}
/* Card without image */
.card:not(:has(img)) {
display: block;
}
Conditional Styling
/* Form with errors */
.form:has(.error) {
border: 2px solid red;
}
/* Article with figures */
.article:has(figure) {
max-width: 800px;
}
Sibling Combinations
/* List item that has a nested list */
li:has(ul) {
font-weight: bold;
}
/* Header followed by nav */
header:has(+ nav) {
border-bottom: 1px solid #ccc;
}
Cascade Layers (@layer)
Control specificity and organize CSS:
Before: Specificity Wars
/* Base styles */
.button { background: blue; }
/* Component styles - needs higher specificity */
.primary-button.primary-button { background: green; }
/* Utility - needs even higher specificity */
.bg-red.bg-red.bg-red { background: red !important; }
After: Cascade Layers
@layer base, components, utilities;
@layer base {
.button { background: blue; }
}
@layer components {
.primary-button { background: green; }
}
@layer utilities {
.bg-red { background: red; }
}
Result: Order matters, not specificity!
Practical Example
@layer reset, design-system, components, overrides;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer design-system {
:root {
--color-primary: #007bff;
--spacing-unit: 8px;
}
}
@layer components {
.button {
padding: var(--spacing-unit);
background: var(--color-primary);
}
}
@layer overrides {
/* Always wins, regardless of specificity */
.special-button {
background: red;
}
}
Nesting
Write nested CSS like Sass, natively:
Before: Flat CSS
.card { }
.card .title { }
.card .content { }
.card:hover { }
.card.featured { }
After: Native Nesting
.card {
/* Base styles */
& .title {
font-size: 1.5rem;
}
& .content {
padding: 1rem;
}
&:hover {
transform: scale(1.05);
}
&.featured {
border: 2px solid gold;
}
}
Complex Nesting
.navigation {
& ul {
list-style: none;
& li {
display: inline-block;
& a {
text-decoration: none;
&:hover {
color: blue;
}
}
}
}
}
Color Functions
Modern color manipulation:
color-mix()
Mix colors without preprocessors:
.button {
--primary: #007bff;
background: var(--primary);
&:hover {
/* Mix with white for lighter shade */
background: color-mix(in srgb, var(--primary) 80%, white);
}
&:active {
/* Mix with black for darker shade */
background: color-mix(in srgb, var(--primary) 80%, black);
}
}
oklch() and oklab()
Perceptually uniform colors:
:root {
/* Lightness, Chroma, Hue */
--primary: oklch(60% 0.15 270);
/* Lighter version - just increase lightness */
--primary-light: oklch(80% 0.15 270);
/* Darker version */
--primary-dark: oklch(40% 0.15 270);
}
color() Function
Use any color space:
.element {
/* Display P3 color space (wider gamut) */
color: color(display-p3 1 0.5 0);
/* Fallback for older browsers */
color: rgb(255, 127, 0);
}
Subgrid
Finally! Grid alignment across nested grids:
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: 1rem;
}
.card {
display: grid;
/* Inherit parent's column tracks */
grid-template-columns: subgrid;
grid-column: span 3;
}
Result: Child grid aligns perfectly with parent grid.
Practical Example
.product-list {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
}
.product {
display: grid;
grid-column: span 3;
grid-template-columns: subgrid;
/* All product images align vertically */
& img {
grid-column: 1;
}
/* All titles align */
& h3 {
grid-column: 2;
}
/* All prices align */
& .price {
grid-column: 3;
}
}
:is() and :where() Selectors
Simplify complex selectors:
:is() for Grouping
/* Before */
.header a:hover,
.footer a:hover,
.sidebar a:hover {
color: blue;
}
/* After */
:is(.header, .footer, .sidebar) a:hover {
color: blue;
}
:where() for Zero Specificity
/* High specificity - hard to override */
:is(.header, .footer) a {
color: blue;
}
/* Zero specificity - easy to override */
:where(.header, .footer) a {
color: blue;
}
/* This works because :where() has 0 specificity */
.custom-link {
color: red;
}
Scroll-Driven Animations
Animate based on scroll position:
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
Scroll Progress Indicator
.progress {
position: fixed;
top: 0;
width: 100%;
height: 4px;
background: linear-gradient(to right, blue var(--scroll), transparent 0);
animation: scroll linear;
animation-timeline: scroll();
}
@property Custom Properties
Typed, animatable CSS variables:
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.animated-gradient {
background: linear-gradient(
var(--gradient-angle),
blue,
purple
);
animation: rotate-gradient 3s linear infinite;
}
@keyframes rotate-gradient {
to {
--gradient-angle: 360deg;
}
}
Color Transitions
@property --color {
syntax: '<color>';
initial-value: red;
inherits: false;
}
.box {
background: var(--color);
transition: --color 1s;
}
.box:hover {
--color: blue;
}
View Transitions API
Smooth page transitions:
/* Define transition */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
/* Customize specific elements */
.card {
view-transition-name: card;
}
::view-transition-old(card) {
animation: fade-out 0.3s;
}
::view-transition-new(card) {
animation: fade-in 0.3s;
}
// Trigger transition in JS
document.startViewTransition(() => {
// Update DOM
updateContent();
});
Logical Properties
Direction-agnostic layouts:
/* Before: Physical properties */
.element {
margin-left: 1rem;
margin-right: 1rem;
border-top: 1px solid;
}
/* After: Logical properties */
.element {
margin-inline: 1rem; /* Left/right in LTR, right/left in RTL */
border-block-start: 1px solid; /* Top in LTR, bottom in RTL */
}
Full Example
.card {
/* Padding on left/right (inline axis) */
padding-inline: 2rem;
/* Padding on top/bottom (block axis) */
padding-block: 1rem;
/* Margin at end of inline direction */
margin-inline-end: 1rem;
/* Border at start of block direction */
border-block-start: 1px solid #ccc;
}
Aspect Ratio
Maintain proportions easily:
/* Before */
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.video-container iframe {
position: absolute;
width: 100%;
height: 100%;
}
/* After */
.video-container {
aspect-ratio: 16 / 9;
}
.video-container iframe {
width: 100%;
height: 100%;
}
Responsive Images
.image {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
Browser Support
Most modern features are well-supported:
/* Feature detection */
@supports (container-type: inline-size) {
.card {
/* Use container queries */
}
}
@supports not (container-type: inline-size) {
.card {
/* Fallback to media queries */
}
}
Conclusion
Modern CSS is incredibly powerful. Features like container queries, :has(), and cascade layers solve problems that previously required JavaScript or complex workarounds.
Start adopting these features today:
- Container queries for truly responsive components
:has()for parent-child relationships- Cascade layers for organized, maintainable CSS
- Native nesting for better developer experience
The future of CSS is here. Are you using it?
Jordan Patel
Webentwickler & Technologie-Enthusiast