Home / Blog / The Latest in CSS 2026
The Latest in CSS 2026

The Latest in CSS 2026

Daniel Kelly
Daniel Kelly
Updated: January 28th 2026

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.

Scroll State Queries

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.

screenshot of slides animating when snapped

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);
        }
      }
    }
  }
}

Customizable Select Elements

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 😅.

screenshot of customizable select elements

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;
}

Tree Counting Functions

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.

screenshot of using sibling-index() to stagger list items transition

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;
  }
}

CSS if() Functions

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

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;
}

Wrapping Up

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.

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

Using Pretext in Vue to Build Variable-Height UI Without Layout Thrash

Using Pretext in Vue to Build Variable-Height UI Without Layout Thrash

Learn how to use Pretext in Vue to measure multiline text without hidden DOM probes, forced reflow, or brittle getBoundingClientRect loops.
Daniel Kelly
Daniel Kelly
Generating Random IDs in Vue.js

Generating Random IDs in Vue.js

How Vue 3.5’s useId() composable gives you stable, unique DOM IDs for forms and accessibility—without manual counters or hydration bugs.
Daniel Kelly
Daniel Kelly
VueSchool logo

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!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.