Google I/O 2025 introduced several CSS features that address long-standing developer pain points.
Chrome 135 introduced new CSS primitives styleable fragmentation, scroll marker elements, and scroll buttons, with the ability to create carousels without JavaScript.
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
.carousel-item {
flex: 0 0 300px;
scroll-snap-align: center;
}
/* New scroll marker elements */
.carousel::scroll-marker {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background: #ccc;
}
.carousel::scroll-marker:checked {
background: #007bff;
}
/* Scroll buttons */
.carousel::scroll-button(prev) {
content: "βΉ";
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
.carousel::scroll-button(next) {
content: "βΊ";
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
The HTML stays simple:
<div class="carousel">
<div class="carousel-item">Item 1</div>
<div class="carousel-item">Item 2</div>
<div class="carousel-item">Item 3</div>
</div>
The experimental Interest Invoker API is available as an origin trial. Itβs like the title attribute.
<button interesttarget="tooltip-1">Hover me</button>
<div id="tooltip-1" popover>
This is a proper tooltip that doesn't look like it's from 1995
</div>
#tooltip-1 {
anchor-name: --tooltip;
position: absolute;
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
border: none;
/* Popover styling */
margin: 0;
inset: unset;
}
#tooltip-1::backdrop {
background: transparent;
}
Combine this with the Anchor Positioning API and Popover API and you get tooltips without JavaScript. The browser handles the show/hide logic based on user interest.
Styleable fragmentation lets you control how content breaks across containers.
.article {
columns: 2;
column-gap: 2rem;
}
.article h2 {
break-before: column;
break-inside: avoid;
}
.article .highlight-box {
break-inside: avoid;
break-after: avoid;
}
/* New fragmentation controls */
.article .pull-quote {
fragment-styling: balance;
break-before: avoid;
break-after: avoid;
}
Now you can prevent awkward text breaks and ensure your content flows naturally across columns, pages, or any fragmented layout.
Scroll markers give you native pagination indicators for scrollable content.
.gallery {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.gallery::scroll-marker-group {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
}
.gallery::scroll-marker {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
border: 2px solid transparent;
cursor: pointer;
}
.gallery::scroll-marker:checked {
background: white;
border-color: #007bff;
}
The browser automatically creates and manages the markers based on your scroll containerβs content. Click a marker, jump to that section. No JavaScript required.
Scroll buttons are like scroll markers but for directional navigation.
.horizontal-scroll {
overflow-x: auto;
position: relative;
}
.horizontal-scroll::scroll-button(prev) {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
z-index: 1;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
cursor: pointer;
content: "βΉ";
display: flex;
align-items: center;
justify-content: center;
}
.horizontal-scroll::scroll-button(next) {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
z-index: 1;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
cursor: pointer;
content: "βΊ";
display: flex;
align-items: center;
justify-content: center;
}
The buttons automatically show/hide based on scroll position. At the start? No previous button. At the end? No next button.
Hereβs how it all comes together in a practical example:
<div class="image-gallery">
<img src="image1.jpg" alt="Image 1" />
<img src="image2.jpg" alt="Image 2" />
<img src="image3.jpg" alt="Image 3" />
<img src="image4.jpg" alt="Image 4" />
</div>
.image-gallery {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
gap: 1rem;
padding: 1rem;
}
.image-gallery img {
flex: 0 0 300px;
height: 200px;
object-fit: cover;
border-radius: 8px;
scroll-snap-align: center;
}
/* Scroll markers for navigation */
.image-gallery::scroll-marker-group {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
}
.image-gallery::scroll-marker {
width: 10px;
height: 10px;
border-radius: 50%;
background: #ddd;
cursor: pointer;
}
.image-gallery::scroll-marker:checked {
background: #007bff;
}
/* Scroll buttons */
.image-gallery::scroll-button(prev),
.image-gallery::scroll-button(next) {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
color: white;
border: none;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.image-gallery::scroll-button(prev) {
left: 10px;
content: "βΉ";
}
.image-gallery::scroll-button(next) {
right: 10px;
content: "βΊ";
}
Chrome 135+ for the carousel features. The Interest Invoker API is in origin trial, so youβll need to register for that. Everything else works in modern browsers.
For fallbacks, the scroll containers still work without the markers and buttons.