explainx / blog
Modern CSS Features: A Complete Guide to CSS in 2026
Modern CSS 2026 guide: container queries, cascade layers, CSS nesting, :has() selector, custom properties, color functions, and CSS as a serious engineering tool.
explainx / blog
Modern CSS 2026 guide: container queries, cascade layers, CSS nesting, :has() selector, custom properties, color functions, and CSS as a serious engineering tool.
Jun 27, 2026
Every professional software project runs in three separate environments: development on your laptop, staging as a private mirror of production, and production where real users live. Understanding why — and how environment variables tie it together — is one of the most practical things a beginner can learn.
Jun 27, 2026
The restaurant analogy, HTTP methods, status codes, JSON, API keys, rate limiting — everything a beginner needs to understand and call APIs, with real working examples in curl, Python, and JavaScript.
Jun 27, 2026
Chrome extensions are small apps that run inside your browser and can modify any web page. This guide walks you through exactly what they are, how the three required files fit together, and a complete working example you can load into Chrome in under ten minutes.
CSS has undergone a renaissance. In 2026, it's no longer just a styling language—it's a serious engineering tool with features that rival what preprocessors offered for over a decade. Container queries, cascade layers, native nesting, :has() selector, and modern color functions have fundamentally changed how we build maintainable, scalable design systems.
This comprehensive guide covers the modern CSS landscape based on 2026 production patterns, with practical examples and adoption insights from real-world teams.
/* Media queries only knew viewport */
@media (min-width: 768px) {
.card { width: 50%; }
}
/* Specificity wars */
.card.card.card { color: red; } /* Hack to increase specificity */
/* No nesting */
.card { }
.card__header { }
.card__title { }
/* Container queries know parent size */
@container (min-width: 400px) {
.card { width: 50%; }
}
/* Cascade layers manage priority */
@layer components {
.card { color: blue; }
}
/* Native nesting */
.card {
&__header {
&__title { }
}
}
Container queries solve the fundamental problem with media queries: they only know about viewport size, not the container a component lives in.
<!-- Same card component in two contexts -->
<aside>
<card /> <!-- 300px wide (sidebar) -->
</aside>
<main>
<card /> <!-- 800px wide (main content) -->
</main>
With media queries:
/* Both cards get same styles based on viewport */
@media (min-width: 768px) {
.card { display: grid; grid-template-columns: 1fr 1fr; }
}
/* Breaks when card is in narrow sidebar on wide viewport! */
/* Define container */
.sidebar,
.main-content {
container-type: inline-size;
container-name: card-container;
}
/* Query container, not viewport */
.card {
/* Mobile-first: single column */
display: grid;
grid-template-columns: 1fr;
}
@container card-container (min-width: 400px) {
.card {
/* Two columns when container is wide enough */
grid-template-columns: 1fr 1fr;
}
}
Result: Card adapts to its container, not viewport. Truly modular components.
.sidebar {
container-type: inline-size;
}
.card {
/* Container query units */
padding: 2cqw; /* 2% of container width */
font-size: 4cqi; /* 4% of container inline size */
gap: 1cqh; /* 1% of container height */
margin: 2cqmin; /* 2% of container's smaller dimension */
}
.grid {
container-type: inline-size;
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.card {
background: white;
border-radius: 8px;
padding: 1rem;
}
/* Compact layout (narrow containers) */
@container (max-width: 350px) {
.card {
&__image { display: none; }
&__title { font-size: 1rem; }
&__description { display: none; }
}
}
/* Medium layout */
@container (min-width: 351px) and (max-width: 600px) {
.card {
&__image { height: 150px; }
&__title { font-size: 1.25rem; }
}
}
/* Expanded layout (wide containers) */
@container (min-width: 601px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1.5rem;
&__image { height: 100%; }
&__title { font-size: 1.5rem; }
}
}
| Browser | Support |
|---|---|
| Chrome 105+ | ✅ Full |
| Edge 105+ | ✅ Full |
| Safari 16+ | ✅ Full |
| Firefox 110+ | ✅ Full |
Adoption: 78% of production websites use container queries in 2026.
Cascade layers (@layer) let you explicitly define priority order for different CSS sources, independent of specificity.
/* Base styles */
.button { background: blue; }
/* Component library */
.btn-primary { background: green; }
/* Your overrides */
.button.button.button { background: red; } /* Hack! */
/* Eventually... */
.button { background: orange !important; } /* Defeat */
/* Define layer order (later = higher priority) */
@layer reset, base, components, utilities;
/* Reset layer (lowest priority) */
@layer reset {
* { margin: 0; padding: 0; }
}
/* Base layer */
@layer base {
button { background: blue; }
}
/* Component layer */
@layer components {
.btn-primary { background: green; }
}
/* Utility layer (highest priority) */
@layer utilities {
.bg-red { background: red; } /* Wins, even with lower specificity */
}
Key insight: Layer order trumps specificity. A simple class in a higher layer beats complex selectors in lower layers.
@layer reset, tokens, base, components, utilities;
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
}
body { margin: 0; }
}
@layer tokens {
:root {
--color-primary: oklch(60% 0.15 250);
--spacing-md: 1rem;
}
}
@layer base {
h1 { font-size: 2rem; }
a { color: var(--color-primary); }
}
@layer components {
.card {
padding: var(--spacing-md);
background: white;
border-radius: 8px;
}
.button {
padding: 0.5rem 1rem;
background: var(--color-primary);
color: white;
border: none;
}
}
@layer utilities {
.hidden { display: none; } /* Utility wins over component styles */
.p-0 { padding: 0; }
}
@layer framework {
@layer reset, base, components;
@layer reset {
/* Framework reset */
}
@layer components {
/* Framework components */
}
}
/* Your app styles (higher priority than entire framework) */
@layer app {
.custom-button { /* Overrides framework */ }
}
CSS nesting is now native—no Sass required.
/* Modern nesting syntax */
.card {
padding: 1rem;
background: white;
& h2 {
font-size: 1.5rem;
color: navy;
}
& p {
color: gray;
}
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
&__header {
border-bottom: 1px solid #eee;
}
@media (min-width: 768px) {
padding: 2rem;
}
}
| Feature | Sass | Native CSS (2026) |
|---|---|---|
| Basic nesting | ✅ | ✅ |
| & parent selector | ✅ | ✅ |
| @media nesting | ✅ | ✅ |
| Compilation needed | ✅ Yes | ❌ No |
| Browser support | N/A | ✅ All modern |
| Mixins | ✅ | ❌ |
| Loops | ✅ | ❌ |
Migration path: Many teams are dropping Sass for vanilla CSS + PostCSS in 2026.
The :has() selector enables parent styling based on children—previously impossible without JavaScript.
Style parent based on child existence:
/* Highlight card if it contains an image */
.card:has(img) {
border: 2px solid gold;
}
/* Style form if it has errors */
form:has(.error) {
border-color: red;
}
/* Grid layout only if enough items */
.container:has(.item:nth-child(4)) {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
Sibling selectors:
/* Style heading if followed by paragraph */
h2:has(+ p) {
margin-bottom: 0.5rem;
}
/* Style list item if it contains a checked checkbox */
li:has(input[type="checkbox"]:checked) {
text-decoration: line-through;
opacity: 0.6;
}
Complex patterns:
/* Article with featured image gets special layout */
article:has(.featured-image) {
display: grid;
grid-template-columns: 1fr 2fr;
.featured-image {
grid-row: 1 / -1;
}
}
/* Navigation with active item gets different styling */
nav:has(.active) {
background: var(--color-primary);
color: white;
}
| Browser | Support |
|---|---|
| Chrome 105+ | ✅ Full |
| Safari 15.4+ | ✅ Full |
| Firefox 121+ | ✅ Full |
| Edge 105+ | ✅ Full |
Adoption: Universal support achieved 2023-2024; widespread production use by 2026.
CSS color has evolved beyond hex codes and rgb().
Why oklch(): Better than HSL—perceptually uniform (equal lightness steps look equally different to humans).
:root {
/* oklch(lightness chroma hue) */
--color-primary: oklch(60% 0.15 250); /* Blue */
--color-primary-light: oklch(80% 0.15 250); /* Lighter (same hue) */
--color-primary-dark: oklch(40% 0.15 250); /* Darker (same hue) */
}
/* Generate palette */
.btn-primary { background: var(--color-primary); }
.btn-primary:hover { background: var(--color-primary-dark); }
:root {
--color-base: oklch(60% 0.15 250);
}
/* Mix with white for tints */
.light-variant {
background: color-mix(in oklch, var(--color-base) 20%, white);
}
/* Mix with black for shades */
.dark-variant {
background: color-mix(in oklch, var(--color-base) 80%, black);
}
/* Mix two theme colors */
.accent {
color: color-mix(in oklch, var(--color-primary), var(--color-secondary));
}
:root {
--color-primary: oklch(60% 0.15 250);
}
/* Derive variations */
.lighter {
/* Increase lightness by 20% */
background: oklch(from var(--color-primary) calc(l + 0.2) c h);
}
.saturated {
/* Increase chroma */
background: oklch(from var(--color-primary) l calc(c + 0.05) h);
}
.rotated {
/* Rotate hue */
background: oklch(from var(--color-primary) l c calc(h + 60));
}
Define custom properties with types and fallbacks:
@property --rotation {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@property --color-primary {
syntax: '<color>';
initial-value: blue;
inherits: true;
}
/* Now animatable! */
.box {
transform: rotate(var(--rotation));
transition: --rotation 0.3s;
}
.box:hover {
--rotation: 45deg; /* Smooth animation */
}
.card {
container-type: inline-size;
--padding-scale: 2; /* Configurable */
padding: calc(var(--padding-scale) * 1cqw);
}
Logical properties adapt to writing direction (LTR, RTL, vertical).
/* Old way (assumes LTR) */
.box {
margin-left: 1rem;
padding-right: 2rem;
border-top: 1px solid;
}
/* Modern way (writing-mode aware) */
.box {
margin-inline-start: 1rem; /* Left in LTR, right in RTL */
padding-inline-end: 2rem; /* Right in LTR, left in RTL */
border-block-start: 1px solid; /* Top in horizontal, left in vertical */
}
Mapping:
| Physical | Logical |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
margin-top | margin-block-start |
margin-bottom | margin-block-end |
width | inline-size |
height | block-size |
Subgrid lets nested grids align with parent grid tracks.
.page-layout {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
}
.card {
grid-column: span 4;
display: grid;
grid-template-columns: subgrid; /* Inherit parent's 4 columns */
&__image { grid-column: span 2; }
&__content { grid-column: span 2; }
}
Combining modern features:
@layer reset, tokens, base, components, utilities;
@layer tokens {
@property --color-primary {
syntax: '<color>';
initial-value: oklch(60% 0.15 250);
inherits: true;
}
:root {
--spacing-unit: 0.25rem;
--font-base: system-ui, sans-serif;
}
}
@layer base {
* { box-sizing: border-box; }
body {
font-family: var(--font-base);
margin: 0;
}
}
@layer components {
.card {
container-type: inline-size;
background: white;
border-radius: 8px;
padding: calc(var(--spacing-unit) * 4);
&:has(.featured) {
border: 2px solid var(--color-primary);
}
@container (min-width: 400px) {
display: grid;
grid-template-columns: 200px 1fr;
gap: calc(var(--spacing-unit) * 6);
}
&__title {
color: oklch(from var(--color-primary) calc(l - 0.2) c h);
font-size: clamp(1rem, 3cqi, );
}
}
}
utilities {
{ : none; }
{
: absolute;
: ;
: ;
: hidden;
}
}
| Feature | Performance Impact |
|---|---|
| Container queries | Minimal (optimized in engines) |
| Cascade layers | None (compile-time) |
| :has() | Moderate (avoid deep nesting) |
| Custom properties | Minimal (small overhead) |
| color-mix() | Minimal (computed once) |
Best practices:
:has(.a :has(.b :has(.c))))| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Container queries | 105+ | 110+ | 16+ | 105+ |
| Cascade layers | 99+ | 97+ | 15.4+ | 99+ |
| CSS nesting | 112+ | 117+ | 16.5+ | 112+ |
| :has() | 105+ | 121+ | 15.4+ | 105+ |
| oklch() | 111+ | 113+ | 15.4+ | 111+ |
| color-mix() | 111+ | 113+ | 16.2+ | 111+ |
| @property | 85+ | 128+ | 16.4+ | 85+ |
| Subgrid | 117+ | 71+ | 16+ | 117+ |
All modern features have full support in 2026.
What you lose:
What you gain:
Recommended stack (2026):
CSS in 2026 is a serious engineering tool. With container queries, cascade layers, :has(), and modern color functions, you can build maintainable, scalable design systems without preprocessors.
Key takeaways:
Start adopting these features—they're universally supported and production-ready.
For AI-powered CSS generation, explore the MCP ecosystem and agent skills for design system automation.
Further reading:
Happy styling!