umma.dev

New in CSS 2025

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.

Example

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

Interest Invoker API: Hover Cards

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: Layout Control

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: Native Pagination

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: Native Navigation

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: "β€Ί";
}

Browser Support

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.