A practical, example-driven reference for Modern CSS features you can use in 2026.
- CSS Container Queries
:has()(parent selector)- Native CSS Nesting
- CSS Custom Properties (scoping + fallbacks)
- Logical Properties (direction-agnostic styling)
subgridcolor-mix()@layer(Cascade Layers)- Scroll Snap
- View Transitions
oklch()colors- Scroll-driven animations (
animation-timeline/view()) @scope(scoped styling)@property(typed custom properties)text-wrap: balance(better headings):where()+:is()(selector helpers)accent-color(native form controls)
What: Style an element based on the size of its container (not the viewport).
Why: Build truly reusable components that adapt anywhere they’re placed.
<div class="card-list">
<article class="card">
<h3>Card title</h3>
<p>Responsive to the container.</p>
</article>
</div>.card-list {
container-type: inline-size; /* enables container queries */
container-name: list;
}
.card {
display: grid;
gap: 0.5rem;
padding: 1rem;
border: 1px solid color-mix(in oklch, CanvasText 20%, transparent);
border-radius: 0.75rem;
}
@container list (min-width: 36rem) {
.card {
grid-template-columns: 1fr 2fr;
align-items: center;
}
}What: Select an element if it contains something matching a selector.
Why: Parent-aware styling without JS (e.g., “highlight card if it contains an error”).
<label class="field">
<span>Email</span>
<input type="email" required value="not-an-email" />
<small class="hint">Enter a valid email.</small>
</label>.field {
display: grid;
gap: 0.25rem;
padding: 0.75rem;
border: 1px solid color-mix(in oklch, CanvasText 18%, transparent);
border-radius: 0.75rem;
}
.field:has(input:invalid) {
border-color: oklch(62% 0.22 25); /* red-ish */
background: color-mix(in oklch, oklch(62% 0.22 25) 10%, Canvas);
}
.field:has(input:invalid) .hint {
color: oklch(62% 0.22 25);
}What: Write nested selectors in plain CSS (similar to Sass), using & for the parent selector when needed.
Why: Cleaner component styles with less repetition.
.button {
padding: 0.6rem 0.9rem;
border-radius: 0.75rem;
border: 1px solid color-mix(in oklch, CanvasText 18%, transparent);
background: Canvas;
color: CanvasText;
&:hover {
background: color-mix(in oklch, CanvasText 6%, Canvas);
}
&[data-variant="primary"] {
background: oklch(62% 0.18 255);
border-color: color-mix(in oklch, oklch(62% 0.18 255) 70%, black);
color: white;
&:hover {
background: color-mix(in oklch, oklch(62% 0.18 255) 88%, black);
}
}
}What: Variables like --brand that can be scoped to a subtree and can use fallbacks via var(--x, fallback).
Why: Theme components, override locally, and keep safe defaults.
<section class="theme theme--ocean">
<button class="cta">Buy now</button>
</section>.theme {
--accent: oklch(62% 0.18 255); /* default */
}
.theme--ocean {
--accent: oklch(70% 0.16 215);
}
.cta {
background: var(--accent, rebeccapurple);
color: white;
border: none;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
}What: Use properties like margin-inline, padding-block, inset-inline, etc.
Why: Your layout automatically works for LTR/RTL and vertical writing modes.
<div class="chip">New</div>.chip {
padding-inline: 0.6rem;
padding-block: 0.25rem;
border-radius: 999px;
border: 1px solid color-mix(in oklch, CanvasText 18%, transparent);
}
/* Equivalent to "left" but direction-aware */
.chip {
margin-inline-start: 1rem;
}What: Let a child grid align to the track sizing of its parent grid.
Why: Perfect alignment across repeated cards/rows without hacks.
<div class="products">
<article class="product">
<h3>Alpha</h3>
<p>Short description.</p>
<button>Buy</button>
</article>
<article class="product">
<h3>Beta</h3>
<p>Much longer description that would normally misalign the button.</p>
<button>Buy</button>
</article>
</div>.products {
display: grid;
gap: 1rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: auto auto auto; /* title / desc / actions */
}
.product {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid; /* inherit the 3 rows */
padding: 1rem;
border: 1px solid color-mix(in oklch, CanvasText 18%, transparent);
border-radius: 0.75rem;
}
.product button {
justify-self: start;
}What: Mix two colors directly in CSS (optionally in a specific color space).
Why: Generate consistent borders/hover states without hardcoding many color values.
:root {
--brand: oklch(62% 0.18 255);
}
.panel {
background: color-mix(in oklch, var(--brand) 8%, Canvas);
border: 1px solid color-mix(in oklch, var(--brand) 22%, transparent);
border-radius: 0.75rem;
padding: 1rem;
}What: Control cascade priority by grouping CSS into named layers.
Why: Predictable overrides (great for design systems + app overrides + utilities).
@layer reset, base, components, utilities;
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
}
@layer base {
:root {
color-scheme: light dark;
}
body {
font-family: system-ui, sans-serif;
}
}
@layer components {
.btn {
padding: 0.6rem 0.9rem;
border-radius: 0.75rem;
}
}
@layer utilities {
.btn {
border-radius: 999px;
} /* intentionally wins inside utilities */
}What: Make scrolling “snap” to child elements.
Why: Great UX for carousels, onboarding screens, horizontal galleries.
<div class="snap-row">
<div class="snap-item">1</div>
<div class="snap-item">2</div>
<div class="snap-item">3</div>
</div>.snap-row {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 80%;
gap: 1rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding: 1rem;
}
.snap-item {
scroll-snap-align: start;
border-radius: 1rem;
min-height: 10rem;
background: color-mix(in oklch, CanvasText 8%, Canvas);
display: grid;
place-items: center;
font-size: 2rem;
}What: Smooth transitions between DOM “states” or page navigations (when supported by your platform/framework).
Why: App-like navigation polish with minimal code.
<button id="toggle">Toggle</button>
<main id="content" class="a">A</main>
<script>
const btn = document.querySelector("#toggle");
const el = document.querySelector("#content");
btn.addEventListener("click", () => {
if (!document.startViewTransition) {
el.classList.toggle("b");
return;
}
document.startViewTransition(() => el.classList.toggle("b"));
});
</script>main {
font-size: 3rem;
padding: 2rem;
border-radius: 1rem;
}
.a {
background: color-mix(in oklch, oklch(70% 0.16 215) 18%, Canvas);
}
.b {
background: color-mix(in oklch, oklch(70% 0.16 120) 18%, Canvas);
}What: A perceptual color space (OKLCH) where adjustments are more visually consistent than RGB/HSL.
Why: Better theming, more predictable “lighter/darker” tweaks.
:root {
--bg: oklch(98% 0.02 250);
--text: oklch(20% 0.02 250);
--accent: oklch(62% 0.18 255);
}
body {
background: var(--bg);
color: var(--text);
}
a {
color: var(--accent);
}What: Drive an animation’s progress based on scroll position (page scroll or element-in-view).
Why: Reveal effects, progress indicators, parallax-like motion—without JS.
<div class="progress"></div>
<div style="height: 200vh;"></div>@keyframes grow {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.progress {
position: fixed;
inset-inline: 0;
inset-block-start: 0;
height: 6px;
transform-origin: left;
background: oklch(62% 0.18 255);
animation: grow linear;
animation-timeline: scroll(root);
}@keyframes fade-up {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
animation: fade-up both;
animation-timeline: view();
animation-range: entry 10% cover 30%;
}What: Limit selector matching to a subtree.
Why: Safer component styling without heavy naming conventions.
@scope (.card) {
:scope {
padding: 1rem;
border-radius: 0.75rem;
border: 1px solid color-mix(in oklch, CanvasText 18%, transparent);
}
h3 {
margin: 0 0 0.5rem;
}
}What: Register a custom property with a type so it can interpolate (animate) safely.
Why: Smooth animations with CSS variables.
@property --fill {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.meter {
--fill: 20%;
background: linear-gradient(
to right,
oklch(62% 0.18 255) var(--fill),
color-mix(in oklch, CanvasText 8%, Canvas) 0
);
transition: --fill 600ms ease;
}
.meter:hover {
--fill: 85%;
}What: Balance line breaks in headings.
Why: Instantly nicer typography.
h1,
h2 {
text-wrap: balance;
}What: Group selectors; :where() has zero specificity, :is() keeps the most specific argument.
Why: Cleaner CSS and fewer specificity fights.
/* zero-specificity base styles */
:where(button, input, select, textarea) {
font: inherit;
}
/* group variants */
.alert:is(.success, .info) {
border-color: oklch(70% 0.12 160);
}What: Tint checkboxes/radios/progress controls.
Why: Better-looking forms with almost no CSS.
:root {
accent-color: oklch(62% 0.18 255);
}