All Modern CSS & HTML Features (2024–2026) - vdsology
- Chapter 12 — All Modern CSS & HTML Features (2024–2026)
- CSS Nesting (Native)
- :has() — The Parent Selector
- Container Queries (@container)
- @layer — Cascade Layers
- CSS Subgrid
- oklch() Colors
- Scroll-Driven Animations
- View Transitions API
- Anchor Positioning
- CSS Logical Properties
- :is() and :where()
- Modern HTML — <dialog>
- Modern HTML — Popover API
- Modern HTML — <details> and <summary>
- Modern HTML — loading="lazy"
- Other Modern CSS Properties
Chapter 12 — All Modern CSS & HTML Features (2024–2026)
CSS Nesting (Native)
Before 2024, nesting required Sass. Now it’s built into every browser.
Old Way — Flat and Repetitive
.card { background: white; }
.card h2 { font-size: 1.25rem; }
.card p { color: #6b7280; }
.card:hover { box-shadow: 0 8px 24px rgba(0,0,0,0.1); }
New Way — Native Nesting
.card {
background: white;
h2 { font-size: 1.25rem; } /* same as .card h2 */
p { color: #6b7280; } /* same as .card p */
&:hover { /* & means .card */
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
h2 { color: #2563eb; } /* .card:hover h2 */
}
}
& means “the current selector.” Without &, nested selectors become descendants. With &, they attach directly:
.button {
&:hover { opacity: 0.9; } /* .button:hover */
&:active { transform: scale(0.96); }
&.large { padding: 20px 40px; } /* .button.large */
}
Media queries nested inside rules:
.card {
padding: 16px;
@media (min-width: 768px) { padding: 24px; }
@media (min-width: 1024px) { padding: 32px; }
}
:has() — The Parent Selector
For 20 years, CSS had no way to style a parent based on its children. :has() solves this.
/* Select .card IF it contains an img */
.card:has(img) { padding: 0; }
/* Style a label when its checkbox is checked */
.option:has(input:checked) {
border-color: #2563eb;
background: rgba(37,99,235,0.05);
}
/* Disable button if any input is invalid */
form:has(input:invalid) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
/* Style parent based on child content */
body:has(.hero) .nav {
background: transparent;
}
Read :has() as “if it has” — like an if statement for styling.
Container Queries (@container)
Media queries check the screen width. Container queries check how much space the parent gives the component.
/* Define the container */
.card-wrapper {
container-type: inline-size;
container-name: card; /* optional name */
}
/* Style based on container size */
@container (max-width: 400px) {
.card { flex-direction: column; }
}
@container (min-width: 400px) {
.card { flex-direction: row; }
}
/* Shorthand */
.sidebar { container: sidebar / inline-size; }
@container sidebar (min-width: 300px) {
.widget { display: grid; }
}
The card adapts to its container’s size, not the screen. Place it anywhere — it figures out the layout automatically.
@layer — Cascade Layers
Explicitly control which group of CSS rules wins — without !important.
/* Declare order — first = lowest priority, last = highest */
@layer reset, base, components, utilities;
@layer reset {
a { color: blue; }
}
@layer components {
.nav-link { color: white; } /* beats anything in reset */
}
@layer utilities {
.text-red { color: red; } /* always wins */
}
/* Import third-party into a low-priority layer */
@import url('library.css') layer(third-party);
@layer third-party, base, components;
/* Now your components always beat the library */
CSS Subgrid
Children inherit and participate in the parent’s grid tracks.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
gap: 20px;
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid; /* inherit parent's row tracks */
}
All card titles align, all card footers align — even with different content lengths.
oklch() Colors
Modern color system. Perceptually uniform and supports wider color gamuts.
/* Old way */
--color-primary: #2563eb;
--color-light: #60a5fa; /* guesswork */
/* New way — oklch(lightness chroma hue) */
--color-primary: oklch(55% 0.2 264);
--color-light: oklch(75% 0.2 264); /* same hue, just lighter L */
--color-dark: oklch(40% 0.2 264); /* same hue, just darker L */
oklch(L C H):
L— Lightness: 0% (black) to 100% (white)C— Chroma (saturation): 0 (gray) to ~0.4 (vivid)H— Hue angle: 0–360 degrees
Same L and C with different H = same lightness and saturation, different color. Predictable and consistent.
/* Build a full palette from one hue */
:root {
--h: 264;
--color-100: oklch(93% 0.03 var(--h));
--color-500: oklch(55% 0.2 var(--h)); /* base */
--color-900: oklch(25% 0.1 var(--h));
}
Scroll-Driven Animations
Animations controlled by scroll position — no JavaScript.
/* Reading progress bar */
.progress-bar {
position: fixed;
top: 0; left: 0;
height: 4px;
background: #2563eb;
transform-origin: left;
animation: progress-grow linear;
animation-timeline: scroll(); /* tied to page scroll */
animation-fill-mode: both;
}
@keyframes progress-grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Reveal elements as they enter the viewport */
.reveal {
opacity: 0;
transform: translateY(30px);
animation: reveal-in linear forwards;
animation-timeline: view(); /* triggers when element is visible */
animation-range: entry 0% entry 40%; /* during first 40% of entry */
}
@keyframes reveal-in {
to { opacity: 1; transform: translateY(0); }
}
View Transitions API
Smooth animations between page states — no JavaScript animation libraries needed.
/* One rule — cross-fade between pages */
@view-transition {
navigation: auto;
}
/* Custom transitions */
::view-transition-old(root) {
animation: slide-out 300ms ease-in forwards;
}
::view-transition-new(root) {
animation: slide-in 300ms ease-out forwards;
}
@keyframes slide-out { to { transform: translateX(-100%); } }
@keyframes slide-in { from { transform: translateX(100%); } }
/* Named element transitions */
.hero-image { view-transition-name: hero; }
/* Browser morphs this element smoothly between pages */
Anchor Positioning
Position any element relative to any other element — not just its parent.
.button { anchor-name: --my-button; }
.tooltip {
position: absolute;
position-anchor: --my-button;
bottom: anchor(top); /* tooltip bottom = button top */
left: anchor(center); /* align centers */
transform: translateX(-50%);
}
/* Automatic fallback positions if it overflows */
.tooltip {
position-try-fallbacks: flip-block, flip-inline;
}
CSS Logical Properties
Direction-agnostic properties that work in RTL and vertical writing modes.
/* Physical — breaks in RTL */
.card { margin-left: 24px; text-align: left; }
/* Logical — works everywhere */
.card { margin-inline-start: 24px; text-align: start; }
| 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 |
Shorthand:
margin-block: 16px; /* top and bottom */
margin-inline: 24px; /* left and right */
padding-block: 12px;
padding-inline: 20px;
:is() and :where()
:is() — Group Selectors
/* Old way */
h1 a:hover, h2 a:hover, h3 a:hover { color: blue; }
/* With :is() */
:is(h1, h2, h3) a:hover { color: blue; }
/* Any state */
button:is(:hover, :focus, :active) { background: darkblue; }
Specificity = highest argument in the list.
:where() — Zero Specificity
/* Easy to override — zero specificity */
:where(h1, h2, h3) { font-weight: bold; }
/* This always overrides even with low specificity */
.article h2 { font-weight: 400; }
Modern HTML — <dialog>
Native modal element — handles focus, Escape key, and overlay automatically.
<dialog id="my-modal">
<h2>Confirm Action</h2>
<p>Are you sure?</p>
<button id="cancel">Cancel</button>
<button id="confirm">Delete</button>
</dialog>
<button id="open">Open Modal</button>
const dialog = document.getElementById('my-modal');
document.getElementById('open').onclick = () => dialog.showModal();
document.getElementById('cancel').onclick = () => dialog.close();
dialog {
border: none;
border-radius: 12px;
padding: 32px;
max-width: 480px;
box-shadow: 0 24px 64px rgba(0,0,0,0.2);
}
dialog::backdrop {
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
}
Modern HTML — Popover API
Native tooltips and dropdowns with zero JavaScript for basic use.
<button popovertarget="my-menu">Open Menu</button>
<div id="my-menu" popover>
<p>Menu item 1</p>
<p>Menu item 2</p>
</div>
Click button → popover opens. Click anywhere else → closes. No JavaScript.
<div popover="auto"> <!-- closes on outside click (default) -->
<div popover="manual"> <!-- only closes explicitly -->
Modern HTML — <details> and <summary>
Native accordion — no JavaScript.
<details>
<summary>What is your return policy?</summary>
<p>You can return any item within 30 days for a full refund.</p>
</details>
details { border: 1px solid #e5e7eb; border-radius: 8px; }
summary { padding: 16px; font-weight: 600; cursor: pointer; }
details[open] summary { color: #2563eb; }
Modern HTML — loading="lazy"
<!-- Loads only when near the viewport -->
<img src="card1.jpg" alt="Card 1" loading="lazy">
<!-- Always pair with width/height to prevent layout shift -->
<img
src="photo.jpg"
alt="photo"
width="800"
height="600"
loading="lazy"
>
Other Modern CSS Properties
aspect-ratio
.video { aspect-ratio: 16 / 9; width: 100%; }
.avatar { aspect-ratio: 1; width: 80px; } /* square */
accent-color
:root { accent-color: #2563eb; }
/* Checkboxes, radios, ranges now use your brand color */
scroll-snap
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel-item {
scroll-snap-align: start;
flex-shrink: 0;
width: 80%;
}
overscroll-behavior
.modal-content {
overflow-y: auto;
overscroll-behavior: contain; /* prevent scroll chaining */
}
color-scheme
:root { color-scheme: light dark; }
/* Scrollbars, inputs, native elements match OS mode */
@starting-style — Entry Animation
.popup { opacity: 1; transition: opacity 300ms; }
@starting-style {
.popup { opacity: 0; } /* what to transition FROM on first render */
}
@scope — Scoped Styles
@scope (.card) {
p { color: blue; } /* only affects p inside .card */
}
@scope (.card) to (.card-footer) {
p { color: blue; } /* .card p, but not inside .card-footer */
}
@property — Typed Custom Properties
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
/* Enables transitions on this custom property */
light-dark() function
:root { color-scheme: light dark; }
.card {
background: light-dark(#ffffff, #1a1a1a);
color: light-dark(#111, #f0f0f0);
}
Tags: #CSS #css #vdsology #webdevelopment #vdsologyCSS #CSS3 #CSSreference #CSSreference2026
Write a comment