The Platform Takes Over Component Logic
Chrome's "CSS Wrapped 2025" report is out, and it documents a year in which the platform handed developers tools that go well beyond visual tweaks. We are seeing CSS move into territory that once required JavaScript: state detection, conditional values, and even scroll-driven UI components. The features below represent the biggest shifts in how interfaces get built.
Customizable Select Drops the JavaScript Habit
The `select` element has been a notorious pain point for styling due to its reliance on internal, browser-rendered UI. With appearance: base-select, you can now restyle the whole control, including the button and dropdown list via ::picker(select) and ::selectedcontent. A feature query ensures graceful degradation for browsers that haven't shipped support yet.
select {
/* Opt-in for the new customizable select */
@supports (appearance: base-select) {
&, &::picker(select) {
appearance: base-select;
}
}
}
What makes this genuinely useful for production is the ability to include rich media — like flags or icons — directly inside options, and to clone that content onto the button. Pseudo-element styling, optgroup structures, and full custom rendering all become first-class CSS concerns.
See the Pen [A customizable select with only pseudo-elements [forked]](https://codepen.io/smashingmag/pen/pvyqqJR) by utilitybend.
Carousels Without a Line of Script
The classic friction between a client's desire for a slider and a developer's fear of jQuery remains a lifestyle issue, but CSS is closing the gap. The ::scroll-marker and ::scroll-button() pseudo-elements bring native scroll indicators and controls to the browser. Coupled with scroll-marker-group for grouping and anchor positioning to fine-tune button placement, a full carousel runs entirely on HTML and CSS.
.carousel {
overflow-x: auto;
scroll-marker-group: after; /* Creates the container for dots */
/* Create the buttons */
&::scroll-button(inline-end),
&::scroll-button(inline-start) {
content: " ";
position: absolute;
/* Use anchor positioning to center them */
position-anchor: --carousel;
top: anchor(center);
}
/* Create the markers on the children */
div {
&::scroll-marker {
content: " ";
width: 24px;
border-radius: 50%;
cursor: pointer;
}
/* Highlight the active marker */
&::scroll-marker:target-current {
background: white;
}
}
}
You can even use the attr() function to pull background images into each marker, producing elegant navigation dots without any JavaScript. This is a significant performance win, but it comes with accessibility considerations that developers still need to test.
See the Pen [Webshop slick slider remake in CSS [forked]](https://codepen.io/smashingmag/pen/gbrZZPY) by utilitybend.
Scroll State: Queries for Sticky and Snapped Elements
Chrome 133 introduced scroll-state queries, letting us answer the question: "is this component stuck, snapped, or overflowing?" Instead of using IntersectionObserver hacks, set container-type: scroll-state on a parent container and style children conditionally.
The feature also exposes the scroll direction, making it possible to trigger effects like adding a drop shadow to a sticky header only at the right moment.
.header-container {
container-type: scroll-state;
position: sticky;
top: 0;
header {
transition: box-shadow 0.5s ease-out;
/* The query checks the state of the container */
@container scroll-state(stuck: top) {
box-shadow: rgba(0, 0, 0, 0.6) 0px 12px 28px 0px;
}
}
}
Combined with anchor positioning, these queries enable responsive UI behaviors like moving a selection frame over a currently snapped item, all in pure CSS.
See the Pen [Scroll-state query to check which item is snapped with CSS, Pokemon version [forked]](https://codepen.io/smashingmag/pen/vEGvvLM) by utilitybend.
Logic Moves Into the Stylesheet
The "Optimized Ergonomics" section of the report highlights a wave of features that fundamentally change how we think about writing CSS logic:
if(): A ternary operator for stylesheets, letting you apply values conditionally inside a property declaration.@function: Custom functions are coming to CSS, promising to reduce repetition and create modifiable snippets for animations, layout values, and more.sibling-index()andsibling-count(): These tree-counting functions make dynamic rows or lists responsive to their own size and position without hard-coding variables into the markup.
Staggering animation effects based on the number of siblings used to require manual indexing or JavaScript. With these tree-counting functions, a single formula handles it automatically:
.card-container > * {
animation: reveal 0.6s ease-out forwards;
/* No more manual --index variables! */
animation-delay: calc(sibling-index() * 0.1s);
}
Anchored Container Queries
After building custom carousels, the next frontier is the evolution of CSS Anchor Positioning: anchored container queries. With @container anchored(fallback: flip-block), the stylesheet can know which fallback position the browser chose when space runs out. That solves the classic tooltip problem where the arrow points up even though the tooltip flipped below its anchor. The CSS engine now lets us respond to the chosen layout path instead of guessing. This promises significant improvements in dynamic positioning logic and the creation of more robust, contextual UI components.
Nested View Transitions for Consistent Depth
One of the trade-offs of the initial View Transitions implementation was a flattened element tree, which broke 3D transforms and nested clipping. With view-transition-group: nearest, you can create nested transition groups within the DOM structure. That means you get to keep clipping effects and three-dimensional rotations active during the entire transition sequence, whereas previous implementations hoisted elements to the top level and lost those visual properties.
.card img {
view-transition-name: photo;
view-transition-group: nearest; /* Keep it nested! */
}
A More Capable Foundation
Beyond the headline features, other developments point in the same direction: moveBefore preserves state for embedded content like iframes and videos when you rearrange the DOM, and attr() is evolving to handle typed values such as colors and dimensions, opening up new possibilities for stylesheet-driven design.
The dual reality is that these are Chrome-first features for now, but their momentum, plus efforts like Interop, makes it likely they will become the standard. Testing and iterating is crucial not just to polish implementations in browsers but also to contribute to the Open UI conversations that shape what the platform ships. The web platform is giving us a more powerful toolkit for building dynamic, native-feeling applications; using it means we can finally offload complex logic and state management from JavaScript to the CSS engine.



