Modern CSS Capabilities in 2026: Layout, Selectors, Color, and More
A survey of the most transformative CSS features that have shipped since 2020, from subgrid to container queries, with the one declaration that best shows each change.
The most common wrong assumption about modern CSS in 2026 is that the big changes are all new layout tools or fancy selectors. What actually transformed the language is a set of single declarations. Each solves a problem you used to solve with JavaScript, preprocessor hacks, or a verbose workaround. Six of them matter more than the rest. Each one is now stable enough to build on. The working front-end developer who writes CSS daily needs to know what shipped, what is safe to use, and what the backup is, without reading five blog posts to find out. This guide gives you that, with the exact shipping dates, the Baseline status, and the one line that changed everything. Every section below routes to a deeper treatment elsewhere on the site. If you need the beginner path, go to web.dev/learn/css or the MDN CSS first-steps guide, then return here for the mechanics.
Subgrid: The One Declaration That Made Grid Complete
Grid Layout was two-dimensional from the start, but until subgrid shipped, a nested grid could not align its tracks to the parent grid’s tracks. You either repeated the track sizing manually, which broke when the parent changed, or you flattened the DOM, which hurt accessibility and semantics. Subgrid fixes that with one line inside a grid item that is itself a grid container:
.grid__child {
display: grid;
grid-template-columns: subgrid; /* inherit parent's column tracks */
}
That one declaration replaces the float grid, the manual track repetition, and the JavaScript that recalculated column positions on resize. Subgrid is Baseline Widely Available, shipping in all engines as of 2023, according to the MDN Baseline dashboard. It works in Chrome 117, Safari 16, and Firefox 71, so you can use it in production today without a backup for any browser released in the last three years.
Watch The Interop Quirk
The design-system author should note the interop quirk: subgrid on the inline axis behaves differently in Firefox than in Chromium when the parent grid uses auto tracks, a known WPT failure that the Interop 2024 project did not fully close. Test subgrid with auto-sized parent tracks before committing a design system to it. The gap is real, but the feature is stable enough that a backup is rarely worth the maintenance cost.
The :has() Relational Pseudo-Class: Selecting by Descendants and Siblings
Before :has(), CSS could select an element based on its own attributes, its ancestors, or its preceding siblings, but never based on what was inside it. That forced JavaScript event listeners for things like form validation styling, card hover states that depend on child content, and parent highlighting when a child is focused. The :has() relational pseudo-class changes the selector language itself, letting you write a rule that matches an element when a descendant or a subsequent sibling matches a given selector:
.card:has(.badge--sale) {
border-color: oklch(0.7 0.15 25);
}
.form-group:has(input:focus) {
outline: 2px solid oklch(0.6 0.2 250);
}
That single selector replaces a JavaScript MutationObserver, a class toggle, and the CSS that depended on that class. The :has() selector is Baseline Newly Available, with Baseline 2023 on MDN, meaning it shipped in all engines within the 30-month rolling window but only recently. It works in Chrome 105, Safari 15.4, and Firefox 121.
Performance And Fallback Strategy
The backup is a @supports selector(:has(*)) check that applies the improvement only when supported, and a base style that works without it. One performance note: a single broad :has() can be more expensive than a long compound selector, because the engine must evaluate the relational match for every candidate element. Keep the argument specific. Prefer :has(> .child) or :has(+ .sibling) over :has(*) where possible. The interop gap is minimal in 2026, but older iOS Safari versions locked to device had no :has() at all, so the @supports backup is still worth shipping for a year.
Container Queries: Responding to the Container, Not the Viewport
Media queries respond to the viewport, which means a component that is narrow inside a wide layout has no way to know it is narrow. Container queries solve that by scoping size-based conditions to a container element, using the cqw and cqh units for lengths relative to that container’s width and height. The one line that changed everything is the container-type declaration on the parent:
.product-card__wrapper {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.product-card__image {
width: 50%;
}
}
That replaces the JavaScript ResizeObserver, the viewport-ratio hacks, and the duplication of component styles for every breakpoint. Container queries are Baseline Newly Available, with Baseline 2023 on MDN, shipping in Chrome 105, Safari 16, and Firefox 110.
Style Queries And Unit Bugs
The real gap is that style queries, which respond to the computed value of a custom property on the container, shipped later and have a narrower support footprint. The container units (cqw, cqh) also have interop bugs in older versions of Safari that shipped container queries, so verify unit behavior if you target Safari 16.x. The backup for container queries is a combination of media queries for the viewport and a default style that assumes the smallest likely container.
The Silent Failure Mode
The design-system author should know that a container query with no container to measure silently falls back to the initial styles, so always set a default on the component before the @container rule. That is the failure mode that confuses new adopters: the query has no container to measure, and the component renders at its base state with no error.
Cascade Layers: Explicit Priority Buckets with @layer
The cascade had a stable order since CSS2: origin, specificity, and source order. Cascade layers formalize that by letting you declare explicit priority buckets that override specificity entirely. The @layer at-rule creates a named layer, and any later layer in the declaration order wins over any earlier layer, regardless of selector specificity. The one line that changed everything is the layer declaration:
@layer reset, theme, components, utilities;
@layer utilities {
.p-1 { padding: 0.25rem; }
}
@layer components {
.card { padding: 1rem; } /* loses to .p-1 despite higher specificity */
}
That one line replaces the specificity arms race: the !important hack, the multi-class selectors, the increasingly nested overrides. Cascade layers are Baseline Newly Available, with Baseline 2022 on MDN, shipping in Chrome 99, Safari 15.4, and Firefox 97.
Fallback And The Unlayered Trap
The backup for older browsers is to compile the layers into a flat file with the correct source order, which Lightning CSS can do automatically. The interop quirk to watch: @layer declarations placed after @import rules can inject styles into unlayered or differently-layered positions, and the ordering of unlayered styles relative to layered ones is a known source of confusion. Unlayered styles always beat layered styles, in every engine, which is the opposite of what many developers expect. The cascade layer priority is ordinal position in the @layer list; later layers override earlier ones, and specificity within a layer does not escape the layer. That is the mental model that stops the debugging session before it starts.
oklch() and the Perceptually Uniform Color Space
HSL and RGB are not perceptually uniform: a 10% change in lightness at the top of the range looks different from a 10% change at the bottom. The oklch() color space fixes that, where the same numerical change looks like the same visual change. That matters for design systems that need consistent contrast ratios, for theme tokens where a shift of one chroma step should feel the same across hues, and for accessibility where contrast calculations must be reliable. The one line that changed everything is the color function itself:
:root {
--brand: oklch(0.72 0.15 25);
--brand-dim: oklch(0.62 0.15 25); /* same hue, same chroma, lighter by the same visual step */
}
That replaces the HSL or RGB definitions and the preprocessor color functions (Sass lighten/darken/mix) that used to be required for perceptually consistent manipulation. oklch() and oklab() are Baseline Newly Available, with Baseline 2023 on MDN, shipping in Chrome 111, Safari 15.4, and Firefox 113.
Fallback And color-mix()
The backup is to provide a hex or rgb() value before the oklch() declaration, since browsers that do not support the function will ignore the invalid declaration and keep the earlier one. The color-mix() function, also Baseline Newly Available from 2023, lets you mix two colors in a specified color space, enabling opacity-relative-to-background without the opacity property, which is useful when you want a translucent effect that does not blend the text itself. The practical advice for the design-system author: define all theme tokens in oklch() and use color-mix() for hover states and emphasis variants, rather than calculating hex values in a tool. The gap is that older Safari versions parse oklch() but produce slightly different gamut mapping for wide-gamut displays, so verify on a reference monitor before shipping a brand color.
Scroll-Driven Animations: Progress Tied to Scroll Position
Scroll-driven animations let an animation progress based on scroll position, using the scroll() and view() timeline functions, instead of JavaScript scroll observers that run on the main thread and can jank. The one line that changed everything is the animation-timeline declaration:
.progress-bar {
animation: grow linear;
animation-timeline: scroll(block root); /* tracks the block scroll of the root */
}
@keyframes grow {
from { width: 0%; }
to { width: 100%; }
}
That replaces the JavaScript scroll event listener that updated a style on every frame. Scroll-driven animations are Baseline Newly Available, shipping in Chrome 115 and Safari 18.0, but Firefox has not shipped them as of the 2026-09-16 check, so the backup is a @supports (animation-timeline: scroll()) check that applies the scroll-driven version only when supported and a default animation or static style otherwise.
Compositor-Only Properties Are The Key
The real gap is that animating any property other than transform and opacity triggers layout or paint on the main thread, and the compositor advantage is lost. Scroll-driven animations only get the compositor acceleration when you animate transform or opacity, so design the scroll effect around those properties. The View Transitions API, which is related but distinct, captures before and after states of a DOM change and morphs between them automatically, but triggering a transition requires JavaScript (document.startViewTransition), and the CSS part is only the animation definition. That API is Baseline Newly Available from 2024, shipping in Chrome 111 and Safari 18.0, with Firefox still absent as of the same check date. For a scroll progress bar, the compositor-only behavior is the reason to use the CSS feature instead of a JavaScript library, but the Firefox gap means you still ship a backup.
CSS in 2026: What Is New Beyond the Six Declarations
The six declarations above are the core, but the annual catalog of what is new since 2020 includes more. CSS nesting, for example, is Baseline Newly Available from 2023, letting you write selectors inside selectors without a preprocessor. The relaxed parsing behavior (allowing element selectors without &) shipped later and inconsistently, and some valid nested CSS is rejected by older implementations that shipped the earlier spec text.
Typography And Scoping
The text-wrap: balance property, Baseline Newly Available from 2024, balances text lines for short headings, but it has no effect on single-line text, which is the first failure mode new users hit. The text-wrap: pretty property is Limited availability, with Chrome 117 and Safari 17.4 shipping it but Firefox not shipped as of the 2026-09-16 check. The @scope at-rule, Baseline Newly Available from 2024, limits selector matching to a subtree, which is useful for component isolation but has interop quirks with shadow DOM.
Tooling And Interop
The Lightning CSS tool, a Rust-based parser, transformer, and minifier, targets modern browser syntax without transpiling to older forms, which means you can write oklch() and container queries in source and ship them as-is to modern browsers, with a backup for legacy ones. The Interop initiative, an annual cross-browser project where vendors agree to fix the same conformance gaps, has closed most of the 2023 and 2024 gaps, but the 2025 project is not the 2026 one, so check the Web Platform Status dashboard for the current list of failing WPT subtests.
Modern CSS Layout Selectors Color: The Working Vocabulary
The vocabulary of modern CSS in 2026 is precise, and using it correctly is what makes a page authoritative. The distinction between CSS Grid Layout and CSS Flexbox is not macro versus micro, but one-axis versus two-axis: flexbox distributes along a single axis, while grid has explicit column and row tracks. Subgrid makes grid the correct choice for many component-internal alignments, because the child grid can inherit the parent’s tracks, which flexbox cannot do. The gap property, which shipped later in flexbox than in grid, adds consistent spacing between items without margin hacks.
Selectors, Queries, And Layers
The :has() relational pseudo-class selects an element based on its descendants or subsequent siblings, which is a different power than the :not() and :is() functions that came earlier. Container Queries respond to a container element’s size, not the viewport, and the cqw and cqh units are relative to that container. Cascade layers use the @layer at-rule, with priority by ordinal position in the layer list.
Color, Animation, And Status
The oklch() and oklab() color spaces are perceptually uniform, and color-mix() mixes two colors in a specified color space. The View Transitions API uses ::view-transition pseudo-elements for the morph animation. Style Queries, which respond to the computed value of a custom property on the container, enable component-variant logic without class toggling. Scroll-driven animations use the scroll() and view() timeline functions. Baseline status tells you whether a feature is newly available or widely available across engines, and the Interop initiative tracks the annual cross-browser conformance work. Lightning CSS is the tool that targets modern syntax without transpiling. The compositor is the GPU-accelerated rendering layer, and only transform and opacity animations get compositor-only treatment. A backup is the style that applies when a feature is not supported, usually tested with @supports.
CSS Features Shipped Since 2020: What Went from Flag to Stable
The annual rollup of features shipped since 2020 is long, but the ones that moved from behind a flag to stable and widely available are the ones you can build on. Grid gap, formerly grid-gap, became gap and works in flexbox and grid everywhere. The aspect-ratio property, Baseline Widely Available since 2021, reserves space before content loads, which reduces layout shift, a Core Web Vitals metric.
Logical Properties And Individual Transforms
The logical properties (margin-inline, padding-block, inset-inline) shipped broadly, making it possible to write layout that respects writing modes without duplication. The individual transform properties (translate, rotate, scale) shipped, letting you compose transforms without overriding a previous one.
Form Controls And Scrolling
The accent-color property, Baseline Widely Available, colors form controls without a full reset. The :focus-visible pseudo-class, Baseline Widely Available, provides keyboard-only focus styling. The @supports selector() function, Baseline Widely Available, allows testing selectors like :has() before using them. The scroll-behavior property, Baseline Widely Available, provides smooth scrolling without JavaScript. The overscroll-behavior property, Baseline Widely Available, prevents scroll chaining. The content-visibility property, Baseline Widely Available, skips rendering offscreen content, improving performance.
Popover, Validation, And Color
The text-wrap: balance property, Baseline Newly Available from 2024, is still young, and text-wrap: pretty is Limited. The ::backdrop pseudo-element, Baseline Widely Available, styles the area behind a dialog or popover. The popover attribute, Baseline Newly Available from 2024, provides a native HTML popover with CSS styling. The @starting-style at-rule, Limited availability, animates elements from their initial state when they first render. The field-sizing property, Limited availability, lets form controls size to their content. The :user-valid and :user-invalid pseudo-classes, Baseline Newly Available from 2023, style form controls after the user interacts with them. The color() function with named color spaces, Baseline Newly Available, offers even more gamut control than oklch().
New CSS Capabilities Overview: What Still Needs a Backup Strategy
The honest answer to the question of what still needs a backup is that every feature in the six core declarations has a Baseline status of Newly Available, not Widely Available, which means the 30-month window has not closed, and some older browsers are outside it. The backup strategy for each is the same: write the base style that works everywhere, then use @supports to layer the improvement. For subgrid, the backup is a grid with explicit tracks on the child, or a flexbox conversion for simple cases. For :has(), the backup is a class toggle in JavaScript or a base style that does not depend on the relational match. For container queries, the backup is a media query that approximates the container width at the viewport level, plus a default style for the smallest container. For @layer, the backup is source-order flattening, which Lightning CSS does automatically. For oklch(), the backup is a hex or rgb() value declared before the oklch() line. For scroll-driven animations, the backup is a static progress bar or a JavaScript scroll observer, but the JavaScript version runs on the main thread and can jank.
The Bigger Gaps
The View Transitions API has a bigger gap: Firefox is not shipped as of the 2026-09-16 check, so a view transition is unavailable in that engine entirely. The CSS nesting relaxed parsing has interop gaps where some valid nested selectors are rejected in older implementations. The @scope at-rule has quirks with shadow DOM. The style queries for custom properties are newer than size queries and have a narrower support. The final answer for the design-system author is to adopt the six core declarations now, ship backups for the next 12 months, and re-check the Baseline dashboard at the start of each quarter because the rolling window closes feature by feature.
What Is Safe to Use Today
A reader skimming any single section should be able to tell what the page is about from that section alone. The question is what is safe to use today, and the answer is: the six core declarations are safe to use with a backup, and three of them (subgrid, :has(), and @layer) are safe to use without a backup in any browser released in the last three years. Container queries, oklch(), and scroll-driven animations require a @supports backup for Firefox and older Safari versions.
How To Verify Safety
The practical test is the Baseline dashboard on MDN or the Web Platform Status dashboard, which shows the shipping date for each engine and the current Baseline category. The Interop initiative reports the number of failing WPT subtests for each feature, which is the real measure of interop gaps, not the shipping date. For the specific case of scroll-driven animations, the compositor-only behavior applies only to transform and opacity, so design the scroll effect around those properties. For container queries, the cqw units have interop bugs in Safari 16.x, so test unit behavior before relying on it. For oklch(), the gamut mapping differs across engines on wide-gamut displays, so verify on a reference monitor. The text-wrap: pretty property is Limited, so do not rely on it for production text. The View Transitions API requires JavaScript to trigger, so it is not purely a CSS feature. The @scope at-rule is Baseline Newly Available but has shadow DOM quirks. The CSS nesting relaxed parsing has inconsistencies in older implementations. The backup for every one of these is the same: write the base style first, then layer on the newer feature.
FAQ
Why does my container query not work?
A container query has no effect if the container element does not have a container-type: inline-size or container-type: size declaration. The query has no container to measure, so the component renders at its base state with no error. Check the parent for the container-type declaration and that the container-name matches the @container rule.
Why is my :has() selector not matching?
The selector inside :has() must be valid in the context. A common mistake is using :has(+ .sibling) when the sibling is not a following sibling, or using :has(> .child) when the child is a descendant but not a direct child. Also, older iOS Safari versions locked to device had no :has() at all, so test with a @supports selector(:has(*)) backup.
Why is text-wrap: balance not changing my heading?
The text-wrap: balance property has no effect on single-line text. It only balances text that wraps to multiple lines. If the heading is short enough to fit on one line, nothing happens. Apply it to a heading that is two or more lines at the target width to see the effect.
Why does my transition not fire?
The value must change in a way that produces computed-value interpolation. Animating from auto to 0 does not transition, display does not transition, and the initial value must be set before the target value. Set the base state in the default rule, then change the property with a class or :hover rule to trigger the transition.
The Single Most Practical Thing to Do Next
Start by auditing your current CSS for the three features that are safe to use without a backup: subgrid, :has(), and @layer. Replace one float grid, one class toggle for card hover styling, and one specificity hack with the modern equivalents. Then add oklch() for a single brand color, with a hex backup. That is the smallest change that moves your codebase into 2026. Do not rewrite everything at once. The backup strategy costs maintenance, and you should only carry it for features you actually use. After that audit, check the Baseline dashboard for the features you need, and set a quarterly reminder to re-review. The Interop initiative closes gaps continuously, so the database is the source of truth, not this page or any other guide. When you have done that, you are ready for container queries and scroll-driven animations, but only when the backup cost is worth the benefit. The most practical thing is to start with the three safe ones, because they are the ones that will still be in your codebase in five years.