Chrome 133 expands attr() and adds scroll-state container queries
Chrome 133 is in beta, and the Chrome team has been publishing a wave of articles and demos covering its new capabilities. Two features stand out: a major expansion of the CSS attr() function, and new scroll-state queries for container queries.
attr() moves beyond content
Up until now, attr() in CSS was limited to the content property, and could only parse string values. Chrome 133 changes that. As Bramus demonstrates, attr() can now be used on any CSS property—including custom properties—with proper type parsing.
<h1>Some text</h1>
The key addition is the type() function, which tells the CSS engine what kind of value the attribute holds. For example, you can define an attribute like data-color and use it to set the element's color property with a fallback:
h1::before {
content: ' (Color: ' attr(data-color) ') ';
}
h1 {
color: attr(data-color type(<color>), #fff)
}
Three pieces are at play here:
- The attribute name (
data-color) - The type declaration (
type(<color>)) - An optional fallback value (
#fff)
The attribute itself is entirely up to you—it's a hook you add in markup that CSS can style against. For numeric values, you don't need the verbose type() syntax. If you want to let an attribute drive the font size, for example, you can do something like this:
<div>Some text</div>
h1 {
color: attr(data-size px, 16);
}
The fallback is optional, so you can omit it when the attribute is always present or when a missing value doesn't matter for the style.
Scroll states become queryable
The second headline feature lets you style elements based on their scroll behavior. Adam Argyle shows how sticky headers, scroll-snapped containers, and scrollable elements can now react to their state through container queries.
The pattern: register a container with a container-type that matches the scrolling behavior you care about. The element being queried can't be the container itself, so you wrap it. For a sticky navigation, the <nav> becomes the queryable container:
.sticky-nav {
container-type: scroll-state;
}
<nav class="sticky-nav">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Blog</a></li>
</ul>
</nav>
You can place the sticky logic directly on the <nav>, since it holds the content that gets stuck:
.sticky-nav {
container-type: scroll-state; /* set a scroll container query */
position: sticky; /* set sticky positioning */
top: 0; /* stick to the top of the page */
}
If you have multiple containers, you can use the container shorthand to assign a container-name; otherwise the longhand properties suffice. Then you query with @container, declaring which container type you're targeting:
@container scroll-state() { }
Inside the query, you specify the state you're looking for. For a sticky header at the top of the viewport, that's stuck: top:
@container scroll-state(stuck: top) {
The contents of the container query can target the stuck element or nested selectors within it. So when the navigation enters its stuck state, you can restyle the element itself, or its links:
.sticky-nav {
border-radius: 12px;
container-type: scroll-state;
position: sticky;
top: 0;
/* When the nav is in a "stuck" state */
@container scroll-state(stuck: top) {
border-radius: 0;
box-shadow: 0 3px 10px hsl(0 0 0 / .25);
width: 100%;
}
}
.sticky-nav {
/* Same as before */
a {
color: #000;
font-size: 1rem;
}
/* When the nav is in a "stuck" state */
@container scroll-state(stuck: top) {
/* Same as before */
a {
color: orangered;
font-size: 1.5rem;
}
}
}
Similarly, stuck: bottom would address a sticky footer, and the same mechanism works for scroll-snapped elements and scroll containers. It's a clean way to handle states that were previously awkward to detect without JavaScript.



