CSS Wrapped 2024

Chrome and CSS in 2024 journeyed
through the forest, over the mountains, and across the seas

2024 has been another amazing year for CSS!

Cross-document view transitions and scroll-driven animations let you add more interactivity to your applications with a few lines of CSS. You can now animate to height: auto;, style scrollbars, and size text inputs to their contents.

Many features have become Baseline Newly Available this year with exclusive accordions, @property, popover, and @starting-style all interoperable across major browsers.

Thanks to the hard work by browser engineers, specification writers, and the input of the community, CSS has taken another great leap forward this year.

Scroll down to explore these exciting additions to the platform.

Stan the Offlineosaur on a skateboard

Join the Chrome DevRel team and a skateboarding Chrome Dino on a journey through the latest CSS launched for Chrome and the web platform in 2024, highlighting 17 new features

Desert scene in low-res bitmap style

# Components

We traveled over the mountains to bring exciting overlay UX with anchor, the power to animate to height auto, easily turn details into an accordion, get more access to details styling, and intrinsically size form elements!

# Field Sizing

Without field-sizing, to create a content-sized input field you had to either guess at an average size of a text field input or use JavaScript to count characters and increase the element height or width as the user entered text. Now it's a CSS one-liner.

textarea, select, input {
  field-sizing: content;
}

The following video demonstrates how a textarea, select, and input can now shrink to fit the size of the placeholder or content value.

The following demo offers a side by side comparison of how inputs behave without field-sizing and with field-sizing.

This will work for any font, any font size, any language and any writing mode. What used to be high effort will now be low effort.

Profile picture

# Animate to height: auto

An often requested CSS feature is the ability to animate to height: auto. A slight variation of that request is to transition the width property instead of the height, or to transition to any of the other intrinsic sizes represented by keywords like min-content, max-content, and fit-content.

From Chrome 129 you can use the interpolate-size property or the calc-size() function to enable smooth transitions and animations from lengths to intrinsic sizing keywords and back.

CSS interpolate-size demo. If your browser has no support, you can check the recording.

The easiest way to enable this behavior is have the entire page opt-in to it by declaring interpolate-size: allow-keywords on :root.

/* Opt-in the whole page to animating to/from intrinsic sizing keywords */
:root {
    interpolate-size: allow-keywords; /* 👈 */
}

In most cases using interpolate-size should be sufficient. If you need more control over things–such as doing calculations with the resulting pixel value, or transitioning between the same two intrinsic sizing keywords use calc-size() instead.

Profile picture

# Exclusive <details>

A common UI pattern on the web is an accordion component. This component consists of several disclosure widgets that individually can be expanded (or collapsed) to reveal (or hide) their content.

This pattern can be created on the web with a few <details> elements. These are typically grouped visually to indicate that they belong together.

To make an exclusive accordion, add a name attribute to the <details> elements. When this attribute is used, multiple <details> elements that have the same name value form a semantic group. When you open one of the <details> elements from the group, the previously opened one will automatically close.

<details name="learn-css">
  <summary>Welcome to Learn CSS!</summary>
  <p>…</p>
</details>
<details name="learn-css">
  <summary>Box Model</summary>
  <p>…</p>
</details>
<details name="learn-css">
  <summary>Selectors</summary>
  <p>…</p>
</details>
Exclusive Accordion demo
Profile picture

# Styleable <details>

From Chrome 131 you have more options to style the structure of <details> and <summary> elements. You can now use these elements when building disclosure or accordion widgets.

In particular, the changes introduced in Chrome 131 enable the use of the `display` property on these elements, and add a ::details-content pseudo-element to style the part that expands and collapses.

For example, to create a horizontal exclusive accordion, apply a flex layout that flows in the row direction to each <details> element.

details {
  display: flex;
  flex-direction: row;
}
Demo: Horizontal exclusive accordion. If your browser has no support, check out the recording.

In addition to using more display types, the content of the <details> element automatically gets wrapped in a ::details-content pseudo-element. All children of the <details> element except the <summary> get slotted into that pseudo.

You can use this pseudo to control the part of the details disclosure element that expands and collapses.

Demo: Material UI accordion. If your browser has no support, check out the recording.
Profile picture

# Anchor Positioning

Anchoring is a fresh and declarative way to position elements relative to each other. It's perfect for menus, tooltips, selects, labels, cards, settings dialogs, and many more. With anchor positioning built into the browser, you can build layered user interfaces without relying on third-party libraries.

It takes two elements to create an anchor relationship: the anchor and positioned element(s).

The anchor is the element that the positioned elements orient to. Turn an element into an anchor with one line of CSS:

.anchor {
  anchor-name: --over-easy;
}

The positioned elements are the elements that are positioned relative to the anchor. These point to the anchor they want to be positioned relative to with position-anchor and a second line of CSS to specify the side or area the positioned should be in.

.positioned-element {
  position: fixed;
  position-anchor: --over-easy;
  position-area: block-end;
}

In the following demo, the cute egg is the anchor and the text "Over-Easy" is the positioned element.

The position-area property offers all sorts of options! The demo uses the logical property value block-end, but there's center, button, and tons more. Una made a GUI to help you visualize the options:

Resources:

Profile picture
Arctic scene in low-res bitmap style

# Interactions

Through the forest we found features for styling the scrollbar, using multi-page view transitions, creating scroll animations and events for scroll snap!

# Custom Scrollbars

Styling scrollbars has long been possible with the ::-webkit-scrollbar-* pseudo-elements. This approach works fine in Chrome and Safari, but was never standardized by the CSS Working Group.

Available from Chrome 121 are the scrollbar-width and scrollbar-color properties to style the width and, respectively, the color of the scrollbar.

.scroller {
  scrollbar-color: hotpink blue;
  scrollbar-width: 10%;
}

These properties are also supported by Firefox and have partial support in Safari.

Custom Scrollbars demo. Use the color inputs to change the colors.
Profile picture

# Cross-Document View Transitions

In 2023, Chrome was the first browser to ship same-document view transitions, an exciting addition to the web platform that allows you to have rich and seamless transitions between various views of your website. This year, Chrome continued to push the web forwards by shipping cross-document view transitions in Chrome 126.

Cross-document view transitions allow you to run a view transition between two separate documents. As a result, you no longer need to rework your website as a SPA to use view transitions. All it takes is a navigation from one page to another, a core primitive that makes the web “the web”.

Recording of the “Stack Navigator” demo that uses cross-document view transitions

To allow a view transition to run between two pages you need to fulfill two conditions: the navigation must be a same-origin navigation and both pages need to opt in to allow the view transition to run. Opting in is done with the following CSS rule:

@view-transition {
  navigation: auto;
}

Once enabled, cross-document view transitions use the same building blocks as same-document view transitions: add the view-transition-name property to the elements that you want to capture, and the animations are powered by CSS animations.

Apart from shipping cross-document view transitions, Chrome also shipped a few extra additions to more easily work with view transitions, such as view-transition-class. These changes were announced at Google I/O ’24 in May.

Play Video
Video from Google I/O ’24: “Multi-page application View Transitions are here”

This year we also welcomed Safari in shipping view transitions and are looking forward to seeing Firefox continue working on their same-document implementation.

Profile picture

# Scroll-Driven Animations

Scroll-driven animations are a common UX pattern on the web. A scroll-driven animation is linked to the scroll position of a scroll container. This means that as you scroll up or down, the linked animation scrubs forward or backward in direct response.

In the following demo, if your browser has support for CSS scroll-driven animations, the images get unclipped as they cross the scrollport.

Demo featuring self-revealing images powered by scroll-driven animations.

To support the launch of scroll-driven animations in Chrome 115, Chrome DevRel created “Unleash the Power of Scroll-Driven Animations”, a 10-part video course that teaches you all there is to know about scroll-driven animations with CSS or JavaScript.

Play Video
The first episode of “Unleash the Power of Scroll-Driven Animations”

Watch this series and become a scroll-driven animations expert.

Resources:

Profile picture

# Scroll Snap Events

Built-in snap events have made previously invisible moments during scrolling, visible, at the right time, and always correct. They are the missing piece of the puzzle that makes scroll snapping a complete solution.

Two new snap events: scrollsnapchange and scrollsnapchanging.

scroller.addEventListener('scrollsnapchange', event=> {
    console.log(event.snapTargetBlock);
    console.log(event.snapTargetInline);
  })

The scrollsnapchange event fires at a similar moment as scrollend, when scroll has rested and the user has stopped interacting with the scroller.

The scrollsnapchangingevent is eager to fire, and calls the callback the moment the scroller has a new snap target. This is useful for instant UX feedback, providing a mechanism for immediate visual updates based on the user's interaction.

scroller.addEventListener('scrollsnapchanging', event=> {
    console.log(event.snapTargetBlock);
    console.log(event.snapTargetInline);
  })

By combining these events together you can create a seamless experience for picking elements with a scroll gesture. The following ruler experience snaps to quarter inch values and uses scroll driven animation to highlight the selected value. The scrollsnapchangingevent is used to immediately update the number input value, while the scrollsnapchange event is used to support and confirm the selected value.

Checkout the article on developer.chrome.com for more details and examples. Also, the following demo link is to a Snap Event visualizer, helping you feel and see the timing of these new events.

Profile picture
Jungle scene in low-res bitmap style

# Developer Experience

Things got even easier after we traveled across the seas.

We discovered ways to simplify backdrop inheritance, a magic color function called light-dark(), safer and smarter variables with @property, a way to specify a starting style for transitions, ways to pop into the top-layer, improved ruby alignment, options for layering text strokes with text fill, and even more ways to relax CSS nesting!

# Backdrop Inheritance

Historically the ::backdrop pseudo-element didn’t inherit from anywhere. From Chrome 122, this ::backdrop pseudo-element has been converted into a tree abiding element, meaning that it inherits any inheritable properties from its originating element.

Thanks to this change it is possible to override custom property values on specific elements and ::backdrop will have access to them. For example, the ::backdrop associated with an open <dialog> element can now access the custom properties available in that <dialog>.

::backdrop demo
Profile picture

# light-dark()

System colors in CSS have the ability to react to the current used color-scheme value. For example, if you declare color: CanvasText in a CSS rule, the color of the matched elements will be either light or dark depending on the color-scheme value.

In the following demo, use the dropdown to control the color-scheme of the div. Because the div is styled with system colors, it supports both light and dark styles.

System colors demo.

The light-dark() function exposes the same capability to developers. This function accepts two arguments, both of which must be a <color>.

:root {
  color-scheme: light dark;
  --primary-color: light-dark(#333, #fafafa);
  --primary-background: light-dark(#e4e4e4, #121212);
  --highlight-color: light-dark(hotpink, lime);
}

By changing the value of color-scheme, either the first or the second value of light-dark() is used.

  • If the used color scheme is light or unknown then the computed value of the first value gets returned.
  • If the used color scheme is dark then the computed value of the second color is returned.
light-dark() demo. Changing the selection changes the color-scheme value

Resources:

Profile picture

# @property

2024 marked the year that @property finally became Baseline Newly available. Having cross-browser support for @property is a very exciting milestone as with @property and its CSS.registerProperty counterpart you can register custom properties to be of a certain type, control their inheritance behavior, and give them an initial value.

@property --myColor {
  syntax: '<color>';
  inherits: false;
  initial-value: hotpink;
}

By registering a custom property to be of a certain type, the browser knows how to interpolate its values when used in transitions and animations.

By animating a custom property, your CSS becomes more concise and also easier to read, as shown in the following snippet that animates the --angle property from 0deg to 360deg.

@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes adjust-angle {
  to {
    --angle: 360deg;
  }
}

div {
  --angle: 0deg;
  animation: 10s adjust-angle linear infinite;
  rotate: var(--angle);
}
A demo with a rotating gradient border. The rotation is done by animating the --angle property which the gradient border uses.
Profile picture

# The Popover API

The Popover API provides ways to build layered interfaces like tooltips, menus, teaching UIs, and more.

To create a popover with default values, all you need is a button to trigger the popover, and an element that is the popover.

<button popovertarget="my-popover">Open Popover</button>

<div id="my-popover" popover>
  <p><p>I am a popover with more information. Hit <kbd>esc</kbd> or click away to close me.<p></p>
</div>
Simple popover demo

Popovers come with built-in support for top layer promotion, light-dismiss functionality, default focus management, and accessibility features.

Profile picture

# Entry effects with @starting-style@starting-style

The @starting-style at-rule is used to define styles for an element before it has received the first style update. When setting those targeted properties to transition with CSS transitions, you can use these starting-styles to create entry effects.

In the following example, newly added <div> elements fade from yellow to their initial background-color which is transparent.

div {
  transition: background-color 0.5s;
  background-color: transparent;

  @starting-style {
    background-color: yellow;
  }
}
@starting-style demo

Another use case is to animate-in dialogs when opened.

dialog[open] {
  opacity: 1;
  transform: scaleX(1);

  @starting-style {
    opacity: 0;
    transform: scaleX(0);
  }
}
@starting-style demo

Don’t forget to set transition-behavior to allow-discrete when animating discretely animatable properties such as display.

Profile picture

# Line-breakable <ruby> + CSS ruby-alignruby-align

From Chrome 128, ruby annotations are line-breakable and you can style ruby text with the ruby-align CSS property. This gives you finer control on how phonetic annotations or other supplemental information above or beside base text with <ruby> should be presented.

With line-breakable ruby, wrapped ruby annotation text gets placed over wrapped base text, achieving ideal text rendering. Compare this before and after:

Rendering result before Chrome 128 with long ruby annotation text.
Rendering result before Chrome 128 with long ruby annotation text.
Rendering result from Chrome 128 with long ruby annotation text.
Rendering result as of Chrome 128 with long ruby annotation text.

The new CSS property ruby-align controls the alignment of ruby base text and ruby annotation text. The property accepts one of the keyword values space-around, space-between, start, and center.

Image showing use-case for ruby-align property.
Image showing use-case for ruby-align property.

To automatically add pinyin to Chinese text and tweak its appearance, use this tool created by yisi(一丝)

Profile picture

# paint-order

When using text-stroke, the paint-order property can control the order that the text fill and desired stroke are stacked or rendered together. This can be useful when you want to ensure that the stroke is rendered on top of the fill.

The default paint order is: fill, stroke, then markers.

The following video shows how the result of the text stroke is undesirable if the text is filled white and then given a black stroke. Switch those around, so the stroke is drawn first and then filled, and the result is quite nice!

The CSS to control this is one line. Set the paint-order to draw the stroke before the fill by specifying those keywords in that order.

h1 {
  paint-order: stroke fill;

  color: white;
  -webkit-text-stroke: 5px black;
}

Try it for yourself in this CodePen:

Some folks say that the ability to control paint-order has finally made text-stroke usable in production. What do you think?

Profile picture

# CSSOM Nested Declarations

To fix some weird quirks with CSS nesting, the CSSNestedDeclarations interface was added to the CSS Nesting spec. With it, declarations that come after style rules no longer shift up.

This means that the following CSS snippet gives the .foo element a green background color instead of a red one, as would happen without CSSNestedDeclarations.

.foo {
    width: fit-content;

    @media screen {
        background-color: red;
    }
    
    background-color: green;
}

With CSSNestedDeclarations the CSS rule serializes to the following, keeping the background-color: green declaration at its original location:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .resolvedSelectorText = ".foo"
  .specificity = (0,1,0)
  .style (CSSStyleDeclaration, 1) =
    - width: fit-content
  .cssRules (CSSRuleList, 2) =
    ↳ CSSMediaRule
    .type = MEDIA_RULE
    .cssRules (CSSRuleList, 1) =
      ↳ CSSNestedDeclarations
        .style (CSSStyleDeclaration, 1) =
          - background-color: red
    ↳ CSSNestedDeclarations
    .style (CSSStyleDeclaration, 1) =
      - background-color: green

This addition landed in Chrome 130 and complements earlier changes that relaxed the parsing of nested styles.

Profile picture