Platform features that replace JavaScript or gradient hacks with declarative CSS. All are experimental and currently Chromium-only. Gate them behind @supports and treat them as progressive enhancement. The list covers iframes that size themselves to their content, carousel markers generated from the scroll container, underline inset control, single-axis scroll containers, and animations that run when a scroll range is crossed. Each section gives the syntax, the accepted values, and the part that usually bites, such as the automatic minimum size that grows a clipped flex or grid item instead of shrinking it. Keep a fallback for every one of them and detect support with the query given in each section.
Responsive iframes: frame-sizing + responsive-embedded-sizing
Embedding a widget (comment thread, form, checkout) used to require the child to postMessage its height to the parent, which then set the iframe height in JS. Now the browser can do it:
iframe.embed {
width: 100%;
frame-sizing: content-height; /* iframe height = embedded document's layout height */
}
The embedded document must opt in, because content size is cross-origin information:
<meta name="responsive-embedded-sizing" content="allow-origins=*" />
Both sides opting in is the security model: a malicious parent could otherwise exfiltrate information about a cross-origin document by observing the laid-out iframe size. allow-origins restricts which parent origins may read the size; combine with the Content-Security-Policy: frame-ancestors header for defence in depth. Fenced frames are excluded from the feature.
- Values:
auto(initial),content-width,content-height,content-inline-size,content-block-size. The logical variants resolve against the iframe'swriting-mode, not the embedded document's. - Applies to replaced elements; not inherited; animation type discrete.
- Dynamic resize: the embedded document calls
window.requestResize()to report an updated size (the size is otherwise set afterDOMContentLoadedand again onload). - Spec: CSS Box Sizing 4. MDN:
frame-sizing,<meta name="responsive-embedded-sizing">.
Scroll markers: scroll-marker-group
Generates carousel dots / table-of-contents markers from the scroll container itself — no JS, no duplicated markup.
ul.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-marker-group: after; /* or before */
}
li::scroll-marker {
content: " ";
}
::scroll-marker-group {
display: flex;
gap: 0.2em;
}
::scroll-marker:target-current {
background: red;
}
- Value:
none | [ before | after ] || [ links | tabs ].before/afterplace the generated::scroll-marker-grouppseudo-element as a sibling before/after the scroll container's children — in both visual order and tab order. Accessibility best practice: match the visual position with the tab order. links(default) — group rolenavigation, marker rolelink; every marker is a tab stop, in order; originating elements' roles are unaffected. Behaves like a navigation list.tabs— group roletablist, marker roletab; the group behaves as a single focusable component (focusgroup) and only the active marker is a tab stop, with arrow keys switching between markers; originating elements get an implicittabpanelrole, and inactive tab content is hidden from the accessibility tree (so no manualinteractivity: inertis needed). Behaves like a tablist.- Active state:
:target-currentmatches the active marker;:target-before/:target-aftermatch markers before/after it in flat tree order. - Existing markup:
scroll-target-group: autoturns an element containing<a>elements (e.g. a table of contents) into a marker group container, soa:target-currenthighlights the current section without pseudo-elements. - Spec: CSS Overflow 5. MDN:
scroll-marker-group.
Underline control: text-decoration-inset
Adjusts the start and end points of a text decoration, so an underline can be shorter, longer, or shifted relative to the text — the effect people previously faked with background gradients.
a {
text-decoration: underline 0.3em limegreen;
text-decoration-inset: -10px; /* outset: longer than the text */
}
.shifted {
text-decoration-inset: 1em -1em;
} /* start right, end left */
- Value:
<length-percentage>{1,2} | auto. Positive insets (shorter), negative outsets (longer). One value applies to both start and end; two values are start then end. autolets the browser inset the ends so two decorated boxes side by side do not read as one continuous decoration — important for Chinese proper-noun underlining (CLREQ), where adjacent proper nouns need separate underlines.autois not the same as the initial0.- Not inherited, and not a constituent of the
text-decorationshorthand. Percentages resolve against the decorating box's inline size (or each box fragment whenbox-decoration-break: clone). - Spec: CSS Text Decoration 4. MDN:
text-decoration-inset.
Single-axis scroll containers: overflow: auto clip
overflow has long been easy to turn into an unwanted two-axis scroller. Historically, specifying a scrollable value on one axis forced the other axis to hidden — which is still a scrollable value, so you got a second scroller (or a scrollbar gutter) you did not want.
Chrome 153 (stable 2026-09-08; the feature was still listed as Beta/Dev/Canary at release-note time) extends overflow to accept a scrollable value together with clip:
.panel {
overflow: auto clip; /* x scrolls, y clips hard */
}
Benefits: position: sticky can be constrained by different ancestor scroll containers per axis, and the clipped axis stays put instead of becoming a hidden scroller.
Detection uses the new named-feature() function from CSS Conditional Rules 5:
@supports named-feature(single-axis-scroll-container) {
.panel {
overflow: auto clip;
}
}
single-axis-scroll-container is one of only two currently defined named features (the other is anchor-position-follows-transforms). The CSSWG intends to add to this list rarely.
Gotcha: min-width: auto blows out instead of shrinking
clip is a non-scrollable overflow value (visible and clip are non-scrollable; scroll, auto, hidden are scrollable and make the box a scroll container). Per CSS Flexbox 1 §4.5 and CSS Grid 1 §6.6, a flex/grid item's automatic minimum size (min-width/min-height: auto) resolves to its content-based minimum size when its overflow in that axis is non-scrollable — and to zero only when the axis is a scroll container.
So a clipped axis keeps the content-based minimum: a nested table or dashboard inside a flex/grid item bulges out instead of shrinking, breaking the layout. Fix by zeroing the minimum explicitly:
.panel > .nested-scroller {
min-width: 0; /* or min-inline-size: 0 */
min-height: 0; /* the clipped axis */
}
Related in the same Chrome 153 release: scroll-axis-lock lets you opt out of the browser's gesture axis-locking, for elements that should always be diagonally scrollable.
Scroll-triggered animations: timeline-trigger + animation-trigger
A normal CSS animation runs on a DocumentTimeline for a fixed time and starts after load. Scroll-driven animations (shipped 2023) are different: progress scrubs from 0% to 100% with scroll, pauses when scrolling stops, and reverses when you scroll back. Scroll-triggered animations, written up by Bramus on 12 December 2025, stay time-based. Crossing a scroll range runs an action such as play or reverse. The usual JavaScript for that is IntersectionObserver. The Chrome demo still keeps that observer as a fallback.
The Chrome post targeted Chrome 145. Bramus later said it shipped in that release, in February 2026. A CSS-Tricks demo note on 19 June 2026 told readers to use Chrome 146. As of 7 October 2026 this is still Chromium-only. MDN marks the properties experimental (pages updated 5 October 2026), and its animation-triggers guide still said no browser supported them. Treat MDN's compat table as behind the Chrome ship, and keep a fallback.
.card {
timeline-trigger: --t view() contain / cover;
trigger-scope: --t;
animation: unclip 0.35s ease-in-out both;
animation-trigger: --t play-forwards play-backwards;
}
timeline-trigger names a trigger, picks a source (view() or scroll()), then an activation range and an optional active range after /. Order is fixed. Omit the active range and it copies the activation range. The active range must enclose the activation range. A view() source defaults to the cover range. entry 100% exit 0% is contain. entry 0% exit 100% is cover. The trigger and the animation do not have to be on the same element. The Chrome card demo uses contain 15% contain 85% / entry 100% exit 0%: it activates in the inner band and stays active until the subject leaves the scrollport. Animating height: auto in that demo also needs interpolate-size: allow-keywords.
animation-trigger is a comma-separated list. Each item is a dashed name, an enter action, and an optional exit action. One action means the exit does nothing.
| Action | On that edge |
|---|---|
play |
Play, resume if paused, or restart if finished, in the current direction |
play-forwards |
play, forcing a positive playback rate |
play-backwards |
play, forcing a negative playback rate |
play-once |
Play through the iteration count once. Resumes a pause. Does not replay a finished animation |
pause |
Pause in place |
replay |
Jump to the start and play |
reset |
Jump to the start and pause |
none |
No action |
play-forwards play-backwards is the enter/leave pair. play-once alone plays a single time. play pause plays inside the range and pauses outside it.
Trigger names are global. The announcement says the last declaration of a name wins. Repeat the same name across elements only inside trigger-scope: --t (or all), which limits that name to the subtree. The same pattern as anchor-scope.
The Animation Triggers draft also defines event triggers. The Chrome 145 behavior above is the timeline trigger. Spec: Animation Triggers. MDN: animation-trigger, <animation-action>. Range visualizer: timeline trigger ranges and the view-timeline ranges tool.
Detect with @supports (timeline-trigger-source: view()).
Browser Support
The features on this page are experimental and Chromium-only as of 2026-10. Use @supports feature queries (@supports (frame-sizing: content-height), @supports named-feature(single-axis-scroll-container), @supports (timeline-trigger-source: view())) and keep a working fallback. For iframes, the postMessage height handshake. For carousels, JS-driven markers. For scroll-triggered motion, IntersectionObserver.
See also: CSS Tools · Frontend Overview · Reflow Repaint And CLS Optimization