CSS in 2026 is more powerful than ever, with features that previously required JavaScript now built natively into the language. From anchor positioning to scroll-driven animations, here's everything you need to know.
🎨 The Modern CSS Feature Set
- Anchor Positioning — Position elements relative to any other element
- Scroll-Driven Animations — Animate based on scroll position, no JS needed
- @starting-style — Animate elements on first render
- Container Queries — Style based on parent size, not viewport
- :has() Selector — The "parent selector" CSS always needed
- View Transitions API — Smooth page transitions natively
1. Anchor Positioning
Position tooltips, popovers, and dropdowns relative to any element — no JavaScript positioning libraries needed.
/* Define the anchor */
.trigger-button {
anchor-name: --my-anchor;
}
/* Position relative to the anchor */
.tooltip {
position: fixed;
position-anchor: --my-anchor;
/* Place below the anchor, centred horizontally */
top: anchor(bottom);
left: anchor(center);
translate: -50% 8px;
/* Auto-flip if it overflows the viewport */
position-try-fallbacks: flip-block, flip-inline;
}
/* Works with any element pair — no wrapper needed! */
2. Scroll-Driven Animations
Create parallax effects, progress indicators, and reveal animations driven purely by scroll — zero JavaScript.
/* Progress bar that fills as you scroll the page */
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(to right, #3b82f6, #8b5cf6);
transform-origin: left;
animation: grow-progress linear;
animation-timeline: scroll();
}
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Fade-in elements as they scroll into view */
.reveal-on-scroll {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: translateY(0); }
}
3. @starting-style — Entry Animations
Animate elements when they first appear in the DOM — perfect for dialogs, toasts, and dynamically added content.
/* Dialog with entry animation */
dialog[open] {
opacity: 1;
transform: scale(1);
transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;
@starting-style {
opacity: 0;
transform: scale(0.9);
}
}
/* Toast notification that slides in */
.toast {
translate: 0 0;
opacity: 1;
transition: translate 0.4s ease-out, opacity 0.4s;
@starting-style {
translate: 100% 0;
opacity: 0;
}
}
4. Container Queries
Style components based on their parent's size, not the viewport. This is the holy grail for component-based design.
/* Define a container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Style based on container width */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1rem;
}
.card-image {
aspect-ratio: 1;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
.card-image {
aspect-ratio: 16/9;
}
}
5. The :has() Selector
Select parents based on their children. This was CSS's most requested feature for over a decade.
/* Style a form group that contains an invalid input */
.form-group:has(input:invalid) {
border-color: red;
}
.form-group:has(input:invalid) .error-message {
display: block;
}
/* Style a card differently if it has an image */
.card:has(img) {
grid-template-rows: 200px auto;
}
.card:not(:has(img)) {
padding-top: 2rem;
}
/* Dark mode toggle without JS */
html:has(#dark-mode:checked) {
color-scheme: dark;
--bg: #0f172a;
--text: #e2e8f0;
}
6. View Transitions API
/* Smooth page transitions — works with MPA and SPA */
@view-transition {
navigation: auto;
}
/* Customise the transition animation */
::view-transition-old(root) {
animation: slide-out 0.3s ease-in;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease-out;
}
@keyframes slide-out {
to { transform: translateX(-100%); opacity: 0; }
}
@keyframes slide-in {
from { transform: translateX(100%); opacity: 0; }
}
/* Named transitions for specific elements */
.hero-image {
view-transition-name: hero;
}
/* The hero image morphs smoothly between pages! */