Guide · Browser support checked 13 November 2026

Modern CSS

The newest CSS features, explained and shown live.

CSS has gained more in the last few years than in the decade before. This guide covers the features worth knowing now: what each one does, a short example, a live demo where it helps, and links to the official docs, the specification and up-to-date browser support.

Layout and responsive design

Components that respond to the space they are given, grids that line up across nested elements, and form fields that size themselves.

Container queries

Widely availableChrome 105 · Edge 105 · Firefox 110 · Safari 16

Style an element based on the size of its container instead of the whole viewport. A card can switch from stacked to side-by-side wherever it is placed: a sidebar, a grid cell or a full-width row.

Drag the bottom-right corner

Under 380px wide this card stacks. Wider, the image sits beside the text.

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

.card {
  display: grid;
  gap: 12px;
}

@container (min-width: 380px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

Container style queries

Newly available · 2026Chrome 111 · Edge 111 · Firefox 151 · Safari 18

Query the value of a custom property on a container. Set one variable such as --variant: featured on a parent and let every child restyle itself, with no extra classes.

CSS
.card {
  --variant: featured;
}

@container style(--variant: featured) {
  .card-title {
    color: var(--accent);
    font-size: 1.5rem;
  }
}

Subgrid

Widely availableChrome 117 · Edge 117 · Firefox 71 · Safari 16

Let a nested element reuse its parent grid's tracks. Titles, body text and buttons in a row of cards line up with each other, however long each card's content is.

CSS
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
}

field-sizing

Newly available · 2026Chrome 123 · Edge 123 · Firefox 152 · Safari 26.2

Make inputs, selects and text areas grow and shrink with what is typed into them. Auto-growing text areas no longer need JavaScript.

Your browser does not support field-sizing yet, so this field keeps a fixed width.

CSS
textarea,
input {
  field-sizing: content;
  min-width: 10ch;
  max-width: 100%;
}

Selectors and architecture

Select parents by what they contain, write nested rules without a preprocessor, and keep specificity under control in large codebases.

:has()

Widely availableChrome 105 · Edge 105 · Firefox 121 · Safari 15.4

The long-awaited parent selector. Style an element based on what it contains or what follows it, such as a form row with an invalid input or a card that holds an image.

The card styles itself with :has(input:checked). No JavaScript.

CSS
.setting:has(input:checked) {
  border-color: teal;
  background: #ccfbf1;
}

form:has(:user-invalid) .submit {
  opacity: 0.5;
}

Native nesting

Widely availableChrome 120 · Edge 120 · Firefox 117 · Safari 17.2

Write nested rules directly in CSS, the way Sass and Less made popular. The & selector refers to the parent rule, and media queries can sit inside the rule they change.

CSS
.button {
  background: teal;

  &:hover {
    background: darkslategray;
  }

  .icon {
    width: 1em;
  }

  @media (width < 600px) {
    width: 100%;
  }
}

Cascade layers

Widely availableChrome 99 · Edge 99 · Firefox 97 · Safari 15.4

Decide which group of styles wins, independent of selector specificity. Put resets, third-party CSS and components in ordered layers so overrides stay predictable.

CSS
@layer reset, vendor, components, utilities;

@import url("library.css") layer(vendor);

@layer components {
  .card { padding: 24px; }
}

@layer utilities {
  .p-0 { padding: 0; }
}

@scope

Newly available · 2026Chrome 143 · Edge 143 · Firefox 146 · Safari 26.4

Limit styles to one part of the page, with an optional lower boundary where they stop. Selectors stay short, and the closest scope wins when components are nested.

CSS
@scope (.card) to (.card-slot) {
  img {
    border-radius: 12px;
  }

  :scope {
    padding: 24px;
  }
}

Color

Perceptually even color spaces, color mixing and derived palettes, all computed by the browser.

oklch() and color-mix()

Widely availableChrome 111 · Edge 111 · Firefox 113 · Safari 16.2

OKLCH describes color as lightness, chroma and hue, so changing one value gives an even, predictable result. color-mix() blends two colors in any color space, which makes tints and shades one line each.

  • 70% white
  • 35% white
  • brand
  • 25% black
  • 50% black
CSS
:root {
  --brand: oklch(0.6 0.15 185);
  --brand-tint: color-mix(in oklch, var(--brand), white 35%);
  --brand-shade: color-mix(in oklch, var(--brand), black 25%);
}

Relative color syntax

Newly available · 2024Chrome 125 · Edge 125 · Firefox 128 · Safari 18

Build a new color from an existing one by adjusting its channels. Rotate the hue, lighten it or make it translucent, all from a single brand variable.

  • h − 120
  • h − 60
  • brand
  • h + 60
  • 50% alpha
CSS
.badge {
  background: oklch(from var(--brand) l c h / 0.15);
  border-color: oklch(from var(--brand) calc(l - 0.1) c h);
}

.accent {
  color: oklch(from var(--brand) l c calc(h + 60));
}

light-dark()

Newly available · 2024Chrome 123 · Edge 123 · Firefox 120 · Safari 17.5

Give a property its light and dark values in one place. The browser picks one based on the element's color-scheme, which can follow the system setting or a toggle.

This box only sets color-scheme. Every color in it comes from light-dark().

CSS
:root {
  color-scheme: light dark;
}

body {
  background: light-dark(#ffffff, #12141f);
  color: light-dark(#0d0f1a, #e8eaf3);
}

Typography

Better line breaking for headings and paragraphs with a single declaration.

text-wrap: balance

Newly available · 2024Chrome 114 · Edge 114 · Firefox 121 · Safari 17.5

Spread a heading's words evenly across its lines instead of leaving one word stranded on the last line. Use it on headings and short text; text-wrap: pretty (further down) is the paragraph version.

wrap

Modern CSS features you can ship today

balance

Modern CSS features you can ship today

CSS
h1,
h2,
h3 {
  text-wrap: balance;
}

Motion and interaction

Menus, entry animations, page transitions and scroll effects that used to need a JavaScript library.

Popover API

Newly available · 2025Chrome 116 · Edge 116 · Firefox 125 · Safari 17

Add the popover attribute and a button with popovertarget to get a menu or tooltip that shows above everything else, closes on Escape or an outside click, and handles focus, without writing JavaScript.

Hello from the top layer. Press Escape or click outside to close. The fade comes from @starting-style.

HTML + CSS
<button popovertarget="menu">Menu</button>
<div id="menu" popover>…</div>

[popover] {
  transition: opacity 0.25s, display 0.25s allow-discrete,
    overlay 0.25s allow-discrete;
}

@starting-style and transition-behavior

Newly available · 2024Chrome 117 · Edge 117 · Firefox 129 · Safari 17.5

Animate elements as they appear, and as they switch to or from display: none. @starting-style sets the values to transition from, and transition-behavior: allow-discrete lets display and overlay take part.

CSS
.dialog {
  opacity: 1;
  transition: opacity 0.3s, display 0.3s allow-discrete;

  @starting-style {
    opacity: 0;
  }
}

.dialog.is-hidden {
  display: none;
  opacity: 0;
}

View transitions

Newly available · 2025Chrome 111 · Edge 111 · Firefox 144 · Safari 18

Animate between two states of a page. The browser captures before and after snapshots and cross-fades them, and elements with a view-transition-name morph from one position to the other.

JS + CSS
document.startViewTransition(() => updateTheDom());

.product-photo {
  view-transition-name: hero;
}

::view-transition-group(hero) {
  animation-duration: 0.4s;
}

Scroll-driven animations

Newly available · 2026Chrome 115 · Edge 115 · Firefox 159 · Safari 26

Tie an animation's progress to scrolling instead of time. Reading-progress bars, parallax and reveal-on-scroll effects run on the compositor without any scroll listeners.

Scroll inside this box. The bar above fills as you go, driven by animation-timeline: scroll().

There is no JavaScript on this page section: the browser links the keyframes directly to the scroll position of the nearest scroll container.

Use view() instead of scroll() to animate an element as it enters and leaves the visible area.

Keep scroll effects subtle, and turn them off for people who prefer reduced motion.

You reached the end.

Your browser does not support scroll-driven animations yet, so the bar stays still.

CSS
@keyframes grow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.progress {
  transform-origin: left;
  animation: grow linear both;
  animation-timeline: scroll();
}

@property

Newly available · 2024Chrome 85 · Edge 85 · Firefox 128 · Safari 16.4

Register a custom property with a type, an initial value and inheritance rules. Typed properties can be animated, so gradients, angles and colors held in variables transition smoothly.

--demo-angle
animates
CSS
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.ring {
  background: conic-gradient(from var(--angle), teal, indigo, teal);
  animation: spin 4s linear infinite;
}

@keyframes spin {
  to { --angle: 360deg; }
}

Values and functions

Math and tree-aware functions that let one rule produce many results.

sin(), cos() and other trigonometry

Widely availableChrome 111 · Edge 111 · Firefox 108 · Safari 15.4

Use sin(), cos(), tan(), atan2() and friends in calc(). Lay items out on a circle, build radial menus or draw charts, as Vektor.css does for its radar charts.

CSS
.dot {
  --angle: calc(var(--step) * 45deg);
  position: absolute;
  left: calc(50% + cos(var(--angle)) * 60px);
  top: calc(50% + sin(var(--angle)) * 60px);
}

round(), mod() and rem()

Newly available · 2024Chrome 125 · Edge 125 · Firefox 118 · Safari 17.2

Snap values to a step and take remainders inside calc(). Round fluid sizes to whole pixels or to a spacing scale, and build repeating patterns.

CSS
.fluid {
  width: round(down, 50vw, 8px);
  margin-left: mod(100vw, 120px);
  --step: rem(var(--index), 4);
}

sibling-index() and sibling-count()

Newly available · 2026Chrome 138 · Edge 138 · Firefox 154 · Safari 26.2

Read an element's position among its siblings, and how many siblings there are, as numbers in calc(). Staggered animation delays and fanned-out layouts no longer need an inline --index on every item.

Your browser does not support sibling-index() yet, so the bars are all the same width.

CSS
.list li {
  width: calc(sibling-index() * 15%);
  animation-delay: calc(sibling-index() * 80ms);
}

.slice {
  rotate: calc(360deg / sibling-count() * sibling-index());
}

On the horizon

These features already ship in at least one engine. Use them as progressive enhancement: wrap them in @supports, or pick values that older browsers can safely ignore.

Anchor positioning

Limited availabilityChrome, Edge and Safari 27; not yet in Firefox

Tether a tooltip or menu to any element and let the browser flip it when it would overflow the screen.

CSS
.trigger { anchor-name: --menu; }

.menu {
  position: absolute;
  position-anchor: --menu;
  position-area: bottom;
  position-try-fallbacks: flip-block;
}

Cross-document view transitions

Limited availabilityChrome, Edge and Safari 18.2; not yet in Firefox

Animate between two separate pages of a multi-page site with one at-rule on both pages.

CSS
@view-transition {
  navigation: auto;
}

text-wrap: pretty

Limited availabilityChrome, Edge and Safari 26; not yet in Firefox

Slower, smarter line breaking for paragraphs that avoids single-word last lines. Older browsers ignore it.

CSS
p {
  text-wrap: pretty;
}

if()

Limited availabilityChrome and Edge only so far

Inline conditionals that choose a value based on a style query, media query or @supports test.

CSS
.button {
  padding: if(
    style(--size: large): 16px 28px;
    else: 10px 18px
  );
}

Custom functions with @function

Limited availabilityChrome and Edge only so far

Define your own reusable CSS functions that take arguments and return a value.

CSS
@function --fluid(--min, --max) {
  result: clamp(var(--min), 4vw, var(--max));
}

h1 { font-size: --fluid(32px, 64px); }

Customizable <select>

Limited availabilityChrome, Edge and Safari 27; not yet in Firefox

Opt a native select into full styling, including its button, picker and options, while keeping its built-in accessibility.

CSS
select,
::picker(select) {
  appearance: base-select;
}

interpolate-size

Limited availabilityChrome and Edge only so far

Transition to and from intrinsic sizes such as height: auto, so accordions can animate open.

CSS
:root {
  interpolate-size: allow-keywords;
}

.panel { transition: height 0.3s; }

corner-shape

Limited availabilityChrome and Edge only so far

Go beyond round corners: squircles, bevels, scoops and notches, all driven by border-radius.

CSS
.card {
  border-radius: 32px;
  corner-shape: squircle;
}

Scroll-state container queries

Limited availabilityChrome and Edge only so far

Style a sticky header when it is stuck, or a carousel item when it is snapped, with a container query.

CSS
.header {
  container-type: scroll-state;
  position: sticky;
  top: 0;
}

@container scroll-state(stuck: top) {
  .header-inner { box-shadow: 0 4px 12px #0002; }
}

Typed attr()

Limited availabilityChrome, Edge and Firefox 155; not yet in Safari

Read an HTML attribute as a number, length or color in any property, not only in content.

CSS
.bar {
  width: attr(data-value type(<percentage>), 0%);
}

reading-flow

Limited availabilityChrome and Edge only so far

Make keyboard and screen-reader order follow a grid or flex layout's visual order, not the source order.

CSS
.grid {
  display: grid;
  reading-flow: grid-rows;
}

Go deeper, or start building.

Browse the official specifications and references, or try these features visually in the Atelier studios.