
It's the beginning of a new year and the CSS landscape is advancing rapidly. Chrome released it's CSS Wrapped 2025 report and it's full of new features and updates! In this article, let's examine a few of the most interesting ones.
If an element is stuck, snapped, or scrollable, you can now use the scroll state function to target and style it accordingly.
For example, let's say we want to limit the width of the navbar and offset it 20px on page load but when it's position reaches the top of the page, it should stick in place and become full width.
No need to use JavaScript. A little CSS can do the trick.
.sticky-nav {
/* Must add on parent element to use scroll-state query */
container-type: scroll-state;
position: sticky;
top: 0;
display: flex;
justify-content: center;
flex-grow: 0;
> nav {
/* styles when not stuck */
width: 800px;
margin-top: 20px;
border-radius: 12px;
padding: 30px;
background: orange;
transition: all 0.3s ease;
/* styles when stuck to top */
@container scroll-state(stuck: top) {
width: 100%;
margin-top: 0;
border-radius: 0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
}
}The key is setting container-type: scroll-state on the sticky element, then using the @container scroll-state(stuck: top) query on a child element to detect when it's stuck. The browser handles all the scroll detection for you—no intersection observers or scroll event listeners required!
This could also be really useful for animating snapped slide elements.

html {
scroll-snap-type: y mandatory;
}
section {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
container-type: scroll-state;
scroll-snap-align: start;
scroll-snap-stop: always;
@supports (container-type: scroll-state) {
@media (prefers-reduced-motion: no-preference) {
> h1 {
transition: opacity 0.5s ease, transform 0.5s ease;
transition-delay: 0.5s;
opacity: 0;
transform: scale(1.25);
@container scroll-state(snapped: block) {
opacity: 1;
transform: scale(1);
}
}
}
}
}It's about time! Finally, in 2025, we can customize the look and feel of the select element. Hopefully by the end of 2026, it will work across all browsers 😅.

It starts with setting the appearance set the base-select.
select {
&::picker(select) {
appearance: base-select;
}
}Then given the following HTML (notice the img tag INSIDE the option tag 🔥):
<select>
<button>
<!--Displays the content of the selected item-->
<selectedcontent></selectedcontent>
<span class="arrow"></span>
</button>
<option>
<img src="https://i.pravatar.cc/150?img=2" />
Avatar 1
</option>
<option>
<img src="https://i.pravatar.cc/150?img=3" />
Avatar 2
</option>
<option>
<img src="https://i.pravatar.cc/150?img=4" />
Avatar 3
</option>
</select>We can style it like this:
/*
Make Select (and the overlay/picker) Customizable
*/
select,
select::picker(select) {
appearance: base-select;
}
/*
Customize the "Dropdown" Icon
(see the HTML for more info)
*/
select::picker-icon {
display: none;
}
select .arrow::after {
content: "👇";
}
/*
Customize the Overlay (Picker)
*/
::picker(select) {
width: 250px;
margin: 0.5rem 0;
border: none;
border-radius: 15px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
/*
Customize Individual Options
And the chosen option when overlay closed
(see the HTML for more info)
*/
option,
selectedcontent,
select button {
display: flex;
gap: 5px;
align-items: center;
}
/*
Customize the currently selected option in the overlay
*/
option::checkmark {
display: none;
}
option:checked {
background: gray;
}
/*
Add any HTML Inside the Option and Style However you'd like
*/
select img {
border-radius: 100%;
width: 50px;
height: 50px;
}Until now, staggering animations required counting the number of elements in a tree. This either involves fragile hardcoded CSS/HTML or JavaScript. The new sibling-index() CSS function make the whole process much simpler while adapting to changes of the number of elements in the DOM automatically.

li {
/* Create a staggered delay. */
/* We subtract 1 because sibling-index() starts at 1, */
/* ensuring the first item starts immediately (0s). */
transition: opacity 0.25s ease, translate 0.25s ease;
transition-delay: calc(0.1s * (sibling-index() - 1));
@starting-style {
opacity: 0;
translate: 1em 0;
}
}With the new if() CSS function, you can now write conditional CSS rules based on media(), supports(), and style() queries for quick and easy one-off property values. For example, this sets the background color to green for larger screens and blue for smaller.
html {
background: if(media(min-width: 1200px): green; else: blue);
}Custom CSS functions are a new way of creating composable, reusable logic to your CSS. They take in arguments and return a valid just like you'd expect. The below examples defines a --half() function that returns the half of a given value.
@function --half(--value) {
result: calc(var(--value) / 2);
}It can then be invoked like this:
div {
width: --half(100px);
}It's most useful when combined with CSS variables.
:root {
--box-size: 100px;
}
@function --half(--value) {
result: calc(var(--value) / 2);
}
.box {
width: var(--box-size);
height: var(--box-size);
background: orange;
}
.box-half {
width: --half(var(--box-size));
height: --half(var(--box-size));
background: blue;
}There are plenty of other new features mentioned in the CSS Wrapped 2025 report. I definitely recommend going through the full thing yourself. It includes thorough explanations and examples for each new feature.
Anytime we can use CSS instead of JavaScript, we should. It's usually faster, more efficient, and more reliable.
Do note that some of the features mentioned here and in the report don't work in all browsers yet so check in with Can I Use to see if what's supported where and what's not.
Finally, if you'd like to learn how to use Tailwind CSS utilty classes to style your webpages, checkout our comprehensive course Tailwind CSS Fundamentals.



Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.