CSS has quietly crossed a threshold. Things we used preprocessors or JavaScript for are now built into the language—and they’re not “future” anymore. Below is a practical tour of what’s worth your time in 2025, with tiny examples you can drop into a project today.
1) Native CSS Nesting (finally)
Write styles the way you think about components—without a preprocessor.
.card {
padding: 1rem;
border: 1px solid var(--border);
& h3 { margin: 0 0 .5rem; }
&:hover { border-color: oklch(0.8 0.1 20); }
}
Why/when: Cleaner, more local styles. Great for design systems and small components alike.
Support tip: Use it by default; fall back to flat selectors only if you absolutely must.
2) Container Queries (+ container query units)
Make components respond to their parent, not the viewport.
/* Declare a container */
.card-grid {
container-type: inline-size;
container-name: cards;
}
/* Adjust layout when the card grid is wide enough */
@container cards (min-width: 600px) {
.card { display: grid; grid-template-columns: 2fr 3fr; gap: 1rem; }
}
/* Units relative to container */
.card { padding: 4cqw; } /* 4% of container width */
Why/when: True, reusable components; no more “one layout fits every screen.”
Support tip: Size queries are broadly ready. Add a simple default layout outside @container as a fallback.
3) The :has() selector (aka “parent-ish” selector)
Style an element based on what it contains or is followed by.
/* Only add a divider if list items have sublists */
ul:has(> li > ul) { border-left: 2px solid color-mix(in oklab, canvasText 20%, transparent); }
/* Tighter spacing when a heading is followed by a paragraph */
h1:has(+ p) { margin-bottom: .25em; }
Why/when: Cleaner HTML, fewer utility classes, smarter states.
Support tip: Wrap in @supports selector(:has(*)) { … } if you need to be cautious.
4) Subgrid (layout that actually lines up)
Let child grids inherit track definitions from a parent.
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: .75rem;
}
.card {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1; /* span all parent columns */
}
Why/when: Perfect alignment for repeated items (cards, media objects, forms).
Support tip: Good across modern browsers—ship it.
5) Scroll-Driven Animations (CSS timelines)
Animate elements based on scroll position—no JS libraries.
.section { view-timeline: --reveal block; }
figure {
animation: fade-in 1s ease both;
animation-timeline: --reveal;
animation-range: entry 0% cover 60%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
Why/when: Subtle, performant motion tied to reading flow.
Support tip: Provide static styles for older browsers; the content should still look fine.
6) View Transitions (SPA feel, MPA simplicity)
Page/state transitions with almost no code—works across pages now.
// SPA-ish example
document.startViewTransition(() => {
document.body.classList.toggle('list');
});
Why/when: Make navigation feel cohesive without a framework-heavy setup.
Support tip: Enhance progressively—no transition is still acceptable.
7) Modern color: color-mix(), relative colors, oklch(), light-dark()
Ship better palettes and dark mode with minimal effort.
:root { --brand: oklch(0.72 0.18 250); color-scheme: light dark; }
.btn {
background: light-dark(white, oklch(0.25 0.03 264));
color: light-dark(black, white);
}
.card { border-color: color-mix(in oklab, var(--brand) 60%, white); }
.card:hover { background: oklch(from var(--brand) calc(l - 0.1) c h / .12); }
Why/when: Consistent color across devices, easy theming, less guesswork.
Support tip: Safe in evergreen browsers; provide neutral fallbacks if needed.
8) New viewport units: dvh, svh, lvh
Fix the classic “100vh on mobile” issue.
.hero {
min-height: 100dvh; /* adjusts as the browser UI shows/hides */
display: grid;
place-items: center;
}
Why/when: Reliable hero sections and full-height panels on mobile.
Support tip: Use dvh primarily; mix with min(100dvh, 100vh) if supporting odd edge cases.
9) Popover API + top layer styling
Accessible, declarative popovers with built-in layering.
<button popovertarget="menu">Open menu</button>
<div id="menu" popover>
<ul>…</ul>
</div>
#menu:popover-open { translate: 0 4px; }
#menu::backdrop { background: rgb(0 0 0 / .35); }
Why/when: Menus, tooltips, trays—without custom focus/escape handling.
Support tip: Works well now; keep simple fallbacks (e.g., always-visible block) where necessary.
10) @scope + cascade layers (@layer)
Keep styles local and the cascade predictable.
@layer base, components, overrides;
@layer base { :root { --gap: 1rem; } }
@scope (.card) { h3 { margin-block: 0; } }
@layer overrides { .dark .card { background: #0b0b0f; } }
Why/when: Safer overrides, fewer fights with global styles.
Support tip: @layer is broadly good; @scope is emerging—treat it as an enhancement.
11) Anchor Positioning (one to watch)
Position a tooltip or menu relative to a trigger without manual JS math.
.trigger { anchor-name: --btn; }
.tooltip {
position: absolute;
position-anchor: --btn;
inset-area: bottom; /* below the trigger */
margin-top: .5rem;
}
Why/when: Tooltips, dropdowns, and callouts that just…line up.
Support tip: Still rolling out; pair with a basic absolute-position fallback.
Quick “ship it” checklist
- Add progressive gates:
@supports selector(:has(*)) { /* use :has() here */ } @supports (view-timeline: --x) { /* scroll-driven animations here */ } @supports (background: light-dark(white, black)) { /* theme colors */ } - Provide calm fallbacks (static layout, no motion) so older browsers still feel finished.
- Prefer component-first CSS: nesting, container queries, subgrid, and layers make it much easier to reason about.
TL;DR
You can confidently use nesting, :has(), subgrid, container size queries, scroll-driven animations, modern color functions, new viewport units, View Transitions, and the Popover API in 2025. Add @supports guards for emerging bits like @scope and anchor positioning, and your UI will degrade gracefully.
Leave a Reply