CSS Frontier 2026

18 features that are rewriting the rules of the web. Every demo on this page is built with the feature it documents. Scroll to begin.

Scroll

Scroll-Driven Animations

Chrome 115+

Animations that respond to scroll position, not time. The animation-timeline property lets you bind any CSS animation to the scroll progress of a container or the visibility of an element in the viewport.

This replaces entire JavaScript libraries — no Intersection Observer, no scroll event listeners, no requestAnimationFrame. The browser handles it on the compositor thread, so it's buttery smooth even on heavy pages.

The progress bar at the top of this section? That's animation-timeline: view(). The shapes below animate as you scroll through. Even the fade-in of every section on this page uses scroll-driven animations.

CSS
.element {
  animation: spin linear both;
  animation-timeline: view();
  animation-range: entry 0% exit 100%;
}

@keyframes spin {
  from { rotate: 0deg; scale: 0.5; }
  50%  { scale: 1; border-radius: 50%; }
  to   { rotate: 360deg; scale: 0.5; }
}

/* Progress bar tied to scroll */
.progress {
  animation: fill linear both;
  animation-timeline: view(block);
  animation-range: contain 0% contain 100%;
}

@keyframes fill {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

View Transitions API

Chrome 111+ / L2: 126+

Native animated transitions between DOM states. document.startViewTransition() snapshots the old state, applies your changes, then animates between old and new using CSS pseudo-elements you can style.

Level 1 handles same-document transitions (SPAs, tab switches). Level 2 extends this across full page navigations — the browser morphs between pages with zero framework code. This is the death of page-transition libraries.

Click the tabs below. The content crossfades and slides via View Transitions. The animation is defined entirely in CSS.

Alpha State

This panel uses view-transition-name to create a smooth morph between content states. No layout thrashing, no FLIP technique — the browser handles the snapshot and animation.

Beta State

The crossfade you see is the default ::view-transition-old and ::view-transition-new pseudo-elements animating. You can customize these transitions per-element.

Gamma State

Level 2 of the API enables this across full page navigations. Add @view-transition { navigation: auto; } to your CSS and the browser handles cross-document morphing.

CSS + JS
.panel {
  view-transition-name: content;
}

::view-transition-old(content) {
  animation: fade-out 0.25s ease;
}

::view-transition-new(content) {
  animation: slide-in 0.3s ease;
}

/* Cross-document (Level 2) */
@view-transition {
  navigation: auto;
}

CSS Anchor Positioning

Chrome 127+

Position any element relative to any other element, anywhere in the DOM. No JavaScript calculations, no getBoundingClientRect(), no resize observers. Just declare an anchor name and reference it.

This replaces Popper.js, Floating UI, and every tooltip-positioning library. The browser handles viewport collision detection, scroll tracking, and dynamic repositioning natively.

Hover the points below. The tooltips are positioned using anchor() functions — the CSS itself defines the spatial relationship.

anchor-name creates the reference point. Any element can be an anchor.
position-anchor + anchor() functions position this tooltip without JS.
The browser handles viewport clamping automatically. Try resizing.
Works with popovers, dialogs, tooltips — any positioned element.
CSS
.trigger {
  anchor-name: --my-anchor;
}

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;

  /* Position below the anchor, centered */
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;

  /* Viewport-aware fallback */
  position-try-fallbacks: flip-block;
}

@starting-style

Chrome 117+

Entry animations from display: none, finally. @starting-style defines the initial state of an element when it first appears in the DOM — before any transitions kick in. Combined with transition-behavior: allow-discrete, you can smoothly animate elements that toggle visibility.

This is the missing piece for CSS-only modals, popovers, and toggled content. No more keyframe workarounds or JavaScript-managed animation states. The TOC button on this page uses @starting-style for its entry animation.

Click the button to toggle the cards. Each animates in from a different direction using @starting-style for the entry state.

Slide Up
Slide Right
Slide Down
Slide Left
Scale In
Fade In
CSS
.card {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 0.4s ease,
    transform 0.4s ease,
    display 0.4s allow-discrete;

  /* Entry state — before first render */
  @starting-style {
    opacity: 0;
    transform: translateY(20px);
  }
}

/* Hidden state */
.card[hidden] {
  opacity: 0;
  transform: translateY(-20px);
  display: none;
}

oklch + color-mix() + Relative Color Syntax

Chrome 111+

A perceptually uniform color space, built into CSS. oklch() gives you lightness, chroma, and hue axes that actually correspond to how humans see color. No more "technically complementary but visually muddy" palette generators.

color-mix() blends any two colors in any color space. Relative color syntax lets you derive new colors by manipulating channels of existing ones — oklch(from var(--base) l c calc(h + 180)) gives you a perfect complement.

This entire page's color system is built on oklch. The hue shifts between sections? A single --hue-primary custom property feeding into oklch-based tokens.

base
tint 80%
tint 50%
shade 80%
shade 50%
complement
+30°
-30°
+150°
+210°
CSS
/* Perceptually uniform color */
.base { background: oklch(0.7 0.18 200); }

/* Tints & shades via color-mix */
.tint {
  background: color-mix(
    in oklch, oklch(0.7 0.18 200) 50%, white
  );
}

/* Complement via relative color */
.complement {
  background: oklch(
    from oklch(0.7 0.18 200)
    l c calc(h + 180)
  );
}

/* Full hue wheel */
background: conic-gradient(
  in oklch,
  oklch(0.7 0.18 0),
  oklch(0.7 0.18 120),
  oklch(0.7 0.18 240),
  oklch(0.7 0.18 360)
);

Container Queries

Chrome 105+

Responsive design based on the container, not the viewport. @container queries let components adapt to the space they're given, not the screen size. This is how responsive components should always have worked.

Every code/demo panel on this page uses a container query to switch between side-by-side and stacked layouts. The cards below sit inside a resizable container — drag the handle to watch them reflow in real time.

This replaces the "put a resize observer on every component" pattern that frameworks have been doing for years.

Adaptive Card

Resize the container to see this card change layouts. Compact → medium → expanded.

Self-Aware

No media queries, no JavaScript. Pure CSS responding to its own container's inline size.

Composable

Drop this component anywhere — sidebar, main content, modal — and it just works.

CSS
.wrapper {
  container-type: inline-size;
  container-name: card-container;
}

/* Compact */
.card { display: grid; gap: 0.5rem; }

/* Medium — icon beside text */
@container card-container (min-width: 400px) {
  .card {
    grid-template-columns: auto 1fr;
  }
}

/* Expanded — full layout */
@container card-container (min-width: 700px) {
  .card {
    grid-template-columns: auto 1fr auto;
  }
}

CSS Nesting

Chrome 120+

Native Sass-style nesting in plain CSS. You can now nest selectors, media queries, and container queries directly inside rule blocks. The & nesting selector refers to the parent, just like preprocessors — except it's native.

Every CSS file in this project uses native nesting. No Sass, no PostCSS, no build step. The browser handles it directly. This is one of those features that quietly changes how you write CSS every day.

The component below is styled entirely with nested CSS. The code shows the natural hierarchy that nesting enables.

Nested Component

Native

This card is styled using native CSS nesting. Hover to see nested state changes.

  • Selectors nest naturally
  • Media queries nest inline
  • & references parent context
CSS
.card {
  background: var(--bg-surface);
  border-radius: 12px;
  padding: 1.5rem;

  /* Nested child */
  & .header {
    display: flex;
    justify-content: space-between;

    & h4 { font-weight: 700; }
    & .badge { font-size: 0.75rem; }
  }

  /* Nested pseudo-class */
  &:hover {
    border-color: var(--accent);
    & .header h4 { color: var(--accent); }
  }

  /* Nested media query */
  @media (width < 600px) {
    padding: 1rem;
  }
}

@scope

Chrome 118+

True style encapsulation without Shadow DOM. @scope limits where your styles apply — both from a root element down and optionally stopping before an inner boundary (donut scope). Same class names, completely different appearances, zero leakage.

Every section on this page uses @scope to isolate its demo styles. That's why sections can define aggressive selectors without conflicting. Below are two zones using identical class names with completely different visual treatments.

Ocean Zone

Same .scope-item class, scoped to ocean theme.

Deep Blue

Cool tones, rounded corners, subtle gradient.

Sunset Zone

Same .scope-item class, scoped to sunset theme.

Warm Glow

Warm tones, sharp edges, bold borders.

CSS
/* Ocean zone */
@scope (.zone--ocean) {
  .item {
    background: oklch(0.3 0.1 220);
    border-radius: 12px;
  }
}

/* Sunset zone — same classes! */
@scope (.zone--sunset) {
  .item {
    background: oklch(0.3 0.12 30);
    border-radius: 2px;
    border-left: 3px solid oklch(0.7 0.2 30);
  }
}

/* Donut scope — style outer, skip inner */
@scope (.wrapper) to (.inner) {
  p { color: var(--accent); }
  /* .inner p is NOT affected */
}

:has() Selector

Chrome 105+

The parent selector CSS always needed. :has() selects an element based on what it contains or what's happening inside it. Check a checkbox and the entire form changes appearance. Focus an input and its label highlights. All CSS, zero JavaScript.

This is the most impactful selector addition since CSS3. It enables reactive UI patterns that previously required JavaScript event listeners and class toggling. The form below is entirely CSS-driven.

CSS
/* Checkbox toggles entire form theme */
.form:has(:checked) {
  background: oklch(0.15 0.02 290);
  color: oklch(0.9 0 0);
}

/* Focused input highlights its label */
.field:has(:focus) .label {
  color: var(--accent);
  translate: 0 -2px;
}

/* Empty required field shows warning */
.field:has(:placeholder-shown:required) {
  border-left: 3px solid oklch(0.7 0.2 25);
}

/* No JS. All :has(). */

Popover API

Chrome 114+

Native popover behavior built into HTML. The popover attribute gives any element toggle-on-click, light-dismiss (close on outside click), top-layer rendering, and focus management — for free. No JavaScript required for basic popovers.

The table of contents button in the bottom-right corner of this page? That's a native popover with anchor positioning and @starting-style animations. Below are the three flavors: auto (light-dismiss), manual (explicit close), and nested.

Auto popover

Closes when you click outside or press Escape. This is the default behavior.

Manual popover

Won't close on outside click. You need to explicitly dismiss it.

Parent popover

Child popover

Nested popovers maintain a stack. Closing the parent closes children too.

HTML + CSS
<button popovertarget="my-pop">
  Toggle
</button>

<div id="my-pop" popover>
  Content here. Click outside to close.
</div>

text-wrap: balance / pretty

Chrome 114+

Typographic line balancing, native in the browser. text-wrap: balance equalizes line widths in short text blocks (headings, captions). text-wrap: pretty prevents orphans in longer paragraphs by adjusting line breaks.

The hero subtitle on this page uses text-wrap: balance. Below is a side-by-side comparison so you can see the visual difference. Resize your window to see how each algorithm handles reflow.

text-wrap: auto

CSS now handles beautiful text wrapping natively

This paragraph uses the default text wrapping algorithm. Notice how the last line might be very short — an orphan. The browser makes no effort to balance line lengths or avoid typographic awkwardness.

text-wrap: balance

CSS now handles beautiful text wrapping natively

This paragraph uses balanced text wrapping. The browser tries to equalize line widths, creating a more visually harmonious block. Best for short text — headings, captions, blockquotes (up to ~6 lines).

text-wrap: pretty

CSS now handles beautiful text wrapping natively

This paragraph uses pretty text wrapping. The browser prevents orphans on the last line by redistributing earlier line breaks. Designed for body text and longer passages where balance would be too aggressive.

CSS
/* Headings — equalize line widths */
h1, h2, h3, blockquote {
  text-wrap: balance;
}

/* Body text — prevent orphans */
p, li {
  text-wrap: pretty;
}

/* That's it. Two lines of CSS replace
   what previously required JS text
   manipulation libraries. */

@property

Chrome 78+

Typed, animatable custom properties. @property lets you register CSS custom properties with a type (number, color, angle, length...), an initial value, and inheritance control. The key unlock: the browser can now interpolate custom properties in transitions and animations.

This page's entire color system — the hue shifts between sections, the animated gradients, the scroll-driven color transitions — all depend on @property. Without it, --hue-primary would be an opaque string that can't animate.

The gradient below has its angle animated via a registered --gradient-angle property. Use the slider to change the hue in real time — it interpolates smoothly because the browser knows it's a <number>.

--hue 270
Gradient angle animating via @property
CSS
@property --gradient-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --hue {
  syntax: "<number>";
  inherits: true;
  initial-value: 270;
}

.gradient {
  background: linear-gradient(
    var(--gradient-angle),
    oklch(0.5 0.2 var(--hue)),
    oklch(0.7 0.2 calc(var(--hue) + 60))
  );
  /* Now this animates smoothly! */
  animation: rotate 6s linear infinite;
  transition: --hue 0.8s ease;
}

@keyframes rotate {
  to { --gradient-angle: 360deg; }
}

Style Container Queries

Chrome 111+

Query the computed style of a container, not just its size. @container style(--theme: dark) lets child components react to custom property values on their parent. This creates a pure-CSS theming system where toggling a single variable cascades through the entire component tree.

Click the toggle below. A single --theme custom property changes on the parent, and the children restyle themselves via style queries.

Card A

Responds to parent's --theme value via @container style()

Card B

Same CSS class, appearance driven by container style query

CSS
.parent {
  container-type: normal;
  --theme: light;
}

/* Default: light theme */
.card {
  background: oklch(0.95 0 0);
  color: oklch(0.2 0 0);
}

/* Respond to style value */
@container style(--theme: dark) {
  .card {
    background: oklch(0.2 0.03 240);
    color: oklch(0.9 0 0);
  }
}

field-sizing

Chrome 120+

Form inputs that grow with their content. field-sizing: content makes textareas, inputs, and selects automatically resize to fit their content. No JavaScript auto-resize hacks, no hidden measurement divs.

Type in the fields below. The left one grows with your content. The right one stays fixed. One CSS property replaces an entire category of JavaScript workarounds.

CSS
textarea {
  field-sizing: content;
  min-height: 2lh; /* minimum 2 lines */
  max-height: 10lh; /* cap at 10 lines */
}

/* Also works on inputs and selects */
input {
  field-sizing: content;
  min-width: 10ch;
}

/* One line. That's it. No JS. */

Masonry Layout

Experimental (Flag Required)

Pinterest-style layouts, native in CSS Grid. grid-template-rows: masonry lets items fill vertical gaps naturally without fixed row heights. No Masonry.js, no Isotope, no column-count hacks.

This is still experimental and requires a browser flag (chrome://flags/#enable-experimental-web-platform-features). But the spec is actively developed and targeted for shipping. The future of gallery layouts is CSS-only.

01
02
03
04
05
06
07
08
09
CSS
.gallery {
  display: grid;
  grid-template-columns:
    repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry;
  gap: 1rem;
}

/* That's the entire implementation.
   Items fill gaps naturally based on
   available space. No JS library. */

Sibling Functions

Chrome 132+

CSS functions that know an element's position among siblings. sibling-index() returns the 1-based index of an element within its parent. sibling-count() returns the total number of siblings. Combined with calc(), you can create data-driven visual patterns without any data attributes or JavaScript.

The rainbow staircase below uses sibling-index() for staggered heights and animation delays, and sibling-count() to distribute hues evenly across the full color wheel.

CSS
.item {
  /* Distribute hues evenly */
  --hue: calc(
    360 / sibling-count() * sibling-index()
  );
  background: oklch(0.7 0.18 var(--hue));

  /* Staggered height */
  height: calc(
    40px + sibling-index() * 12px
  );

  /* Staggered animation delay */
  animation-delay: calc(
    sibling-index() * 0.05s
  );
}

/* No nth-child(), no data attributes,
   no JavaScript counters. */

Customizable <select>

Chrome 134+ (Experimental)

Finally: fully styleable native select elements. appearance: base-select opts into the new customizable select, letting you style the dropdown, options, selected state, and picker icon with regular CSS while keeping full native semantics, keyboard navigation, and accessibility.

No more fake selects built from divs. No more aria-listbox hacks. The ::picker(select) pseudo-element targets the dropdown, and individual <option> elements can contain rich HTML content.

CSS
select {
  appearance: base-select;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 1rem;
}

/* Style the dropdown */
select::picker(select) {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.5rem;
}

/* Style individual options */
option {
  padding: 0.75rem;
  border-radius: 8px;
}

option:checked {
  background: var(--accent-dim);
}