::scroll-marker-group

Daniel Schwarz on

Get affordable and hassle-free WordPress hosting plans with Cloudways — start your free trial today.

Experimental: Check browser support before using this in production.

The ::scroll-marker-group pseudo-element holds the scroll markers generated by the ::scroll-marker pseudo-element. When clicked on, these scroll markers scroll users to a corresponding scroll target within a scroll container in a scroll-snapping fashion. All in all, scroll markers give users an additional way to navigate overflow content.

You can select ::scroll-marker-group like this:

.scroll-container::scroll-marker-group {
  /* ... */
}

When you ::scroll-marker like this (these get put inside ::scroll-marker-group):

.scroll-target::scroll-marker {
  /* ... */
}

…the HTML result gets rendered like this:

<ul class="scroll-container">
  <!-- Five scroll targets -->
  <li class="scroll-target"></li>
  <li class="scroll-target"></li>
  <li class="scroll-target"></li>
  <li class="scroll-target"></li>
  <li class="scroll-target"></li>

  <::scroll-marker-group>
    <!-- Five scroll markers -->
    <::scroll-marker>
    <::scroll-marker>
    <::scroll-marker>
    <::scroll-marker>
    <::scroll-marker>
  </::scroll-marker-group>
</ul>

Scroll markers are ideal for carousels, scroll snapping components, and any other components where you’d scroll overflow content. You can combine them with ::scroll-buttons, other scroll-based pseudo-elements, other scroll-based features, and a wide range of CSS features in general. They’re quite versatile.

::scroll-marker-group and ::scroll-marker are fully accessible, too. Users can easily navigate the scroll markers with a keyboard and other types of assistive technologies, and screen readers will label them descriptively right out of the box.

Syntax

::scroll-marker-group {
  /* ... */
}

Basic usage

To select all ::scroll-marker-groups:

::scroll-marker-group {
  /* ... */
}

To select the ::scroll-marker-group of a specific scroll container:

.scroll-container::scroll-marker-group {
  /* ... */
}

There are some additional requirements, though:

  • The container must be a scroll container (i.e., its overflow property must not compute to visible).
  • The scroll container must set the scroll-marker-group property to either before or after.
  • The scroll markers must have valid content properties (or they won’t show).

Therefore, your CSS should start off something like this:

.scroll-container {
  /* Required */
  scroll-marker-group: after; /* before | after */

  &::scroll-marker-group {
    /* ... */
  }

  .scroll-target::scroll-marker {
    /* Required */
    content: ""; /* Any valid value */
  }
}

The purpose of scroll-marker-group is to help you to accessibly match the tab order to the visual order, so if the scroll markers appear before the scroll component’s content (roughly, at least), you’d use the before value. Otherwise, you’d use after. The default value is none, so if you don’t set scroll-marker-group to either before or after, the ::scroll-marker-group pseudo-element and the scroll markers within it simply won’t appear.

The example below is a fully-functional carousel that you can navigate using scroll markers. The ::scroll-marker-group pseudo-element uses anchor positioning to position itself relative to the scroll container, which is probably the best way to go about it, but any method of alignment should work fine. And since the scroll markers are positioned at the bottom, the scroll-marker-group property is set to after.

It also uses Flexbox to lay out the scroll markers within.

<ul class="carousel">
  <li style="background:hsl(10 70% 50%)"></li>
  <li style="background:hsl(30 70% 50%)"></li>
  <li style="background:hsl(50 70% 50%)"></li>
  <li style="background:hsl(70 70% 50%)"></li>
  <li style="background:hsl(90 70% 50%)"></li>
</ul>
.carousel {
  /* The width */
  --carousel-width: 100vw;
  width: var(--carousel-width);
  /* The height is half the width */
  aspect-ratio: 1 / 0.5;
  /* Implies flex-direction: row */
  display: flex;

  li {
    /* Give carousel items the same width */
    width: var(--carousel-width);
    /* Prevent flexbox from overwriting said width */
    flex-shrink: 0;
  }

  /* Show only one carousel item */
  overflow: hidden;
  /* Turn the carousel into an anchor */
  anchor-name: --carousel;
  /* Enable smooth scrolling */
  scroll-behavior: smooth;
  /* Place after the content */
  scroll-marker-group: after;

  &::scroll-marker-group {
    /* Space the markers apart */
    display: flex;
    gap: 10px;
    /* Anchor it to the carousel */
    position: fixed;
    position-anchor: --carousel;
    /* Anchor it horizontally */
    justify-self: anchor-center;
    /* Anchor it near the bottom */
    bottom: calc(anchor(bottom) + 10px);
  }

  li::scroll-marker {
    /* Stylized markers */
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 10px;
    border: 1px solid black;
  }

  /* The currently selected marker */
  li::scroll-marker:target-current {
    background: black;
  }
}

Example: Tabs

The following example is fundamentally the same. The key difference is that the scroll markers are styled as tabs and positioned at the top, so as you’d expect this means that the scroll-marker-group property is set to before this time.

You shouldn’t have any trouble choosing between before and after. Even in the example below where ::scroll-marker-group is positioned to the right, we can confidently say that scroll-marker-group: after is correct assuming that the writing mode is left-to-right.

Browser support

We can detect browser support for it, if needed:

@supports selector(::scroll-marker-group) {
  /* ::scroll-marker-group supported */
}

@supports not selector(::scroll-marker-group) {
  /* ::scroll-marker-group not supported */
}

The same thing in JavaScript:

if (CSS.supports("selector(::scroll-marker-group)")) {
  /* ::scroll-marker-group supported */
}

if (!CSS.supports("selector(::scroll-marker-group)")) {
  /* ::scroll-marker-group not supported */
}

Specification

The ::scroll-marker-group pseudo-element is defined in the CSS Overflow Module Level 5 specification, which is currently in Working Draft status. This means that the information can change between now and the time when it becomes adopted as a formal Candidate Recommendation for browsers to implement.

More information