This website is currently in the process of being refactored and redesigned. So if anything looks broken, or simply looks like it's been "designed by a developer", that's why.

Are you familiar with the Scrollspy UI pattern?

You might be thinking: “Duh! Of course I am! Who isn’t?”

I don’t know about you, but I’ve only recently learned that the “highlight the active link when its target section scrolls into view” pattern is what designers refer to when they talk about the Scrollspy effect. I even did some googling and found out that the name originates from Bootstrap, which has ​a documentation page​ dedicated to this pattern.

Anyway…

The Scrollspy effect is based on the idea that you “spy 🕵️” on sections of content on a page (using JavaScript and something like the ​Intersection Observer API​), and when a specific part or section of the content scrolls into view, you visually highlight the link to that section. This pattern is most commonly implemented in one-page websites with a fixed header and a nabber (like the live example in the Bootstrap documentation), or as sticky Tables of Content on article pages, like the one on MDN, for example.

Here’s a video of an MDN article page showing the links in the sticky table of contents on the right side of the article get highlighted as you scroll through the sections in the article.

Historically, we’ve had to use JavaScript to create this effect. Typically it involved adding a .active class name to the active link.

If you inspect the links on the MDN website, you’ll notice that the highlight styles are applied to a link when the link gets an aria-current=true attribute. This attribute is added via JavaScript and used as a styling hook in CSS to style the active link. Here’s a screenshot showing the aria-current attribute on the active link visible in the Elements panel of the browser DevTools.

A screenshot of the browser devtools showing the aria-current attribute used on the MDN website for active links. It also shows the ARIA attributes used as styling hooks in the CSS panel.

Using ARIA attributes as styling hooks is one of my favorite ways to use ARIA attributes while enforcing accessibility requirements in projects.

Today, it is possible to recreate the Scrollspy effect without JavaScript.

Using a new experimental CSS feature, the browser (currently only Chrome) will do the “spying” for you (no, I’m not referring to the privacy issues here 😜) when you use the new property: scroll-target-group.

The scroll-target-group property has ​recently been added​ to the CSS Overflow Module Level 5.

If you’ve read ​my previous article examining the accessibility of "CSS Carousels"​, then you’re already familiar with this specification (and you, too, may have very strong opinions and feelings about it 🤗). If you haven’t read the article, I highly recommend reading it as it will give you some relevant context for the technical discussion in this article.

The scroll-target-group property is similar in concept to the scroll-marker-group property, with one important difference:

The scroll-marker-group is used to generate CSS scroll markers in the form of CSS pseudo-elements. The ::scroll-markers and their containing element are generated by the browser from your CSS. The CSS-generated markers come with certain behavior built into them provided by the browser, and the active scroll marker within the scroll marker group can be styled using the :target-current pseudo-selector. (This implementation currently has unresolved accessibility issues that I discussed in the CSS Carousels accessibility article.)

The scroll-target-group property, on the other hand, is meant to be used on an HTML container element, and is used to enrich HTML anchor elements functionality to match the pseudo elements one, which makes it possible to use the :target-current selector to highlight links when their respective targets are in view. 🙌🏻

In other words, scroll-target-group can be used to promote ‘regular’ HTML anchor elements (<a href="">) to become scroll markers. The browser will then use a specific algorithm to determine which anchor is the active anchor within the group, just like it determines the active marker within a group of CSS ::scroll-markers. The active anchor then matches the :target-current selector, which you can use to visually highlight the active link.

All you need to use this feature is to start with semantic markup for the group of anchors. For example, you might start with a <nav> landmark region containing an ordered list of links to sections within an article:

<nav aria-labelledby="toc-label">
<span id="toc-label" hidden>Table of Contents</span>
<ol role="list">
<li><a href="#one">Section One</a></li>
<li><a href="#two">Section Two</a></li>
<li><a href="#three">Section Three</a></li>
<li><a href="#four">Section Four</a></li>
<li><a href="#five">Section Five</a></li>
</ol>
</nav>

Then, you instruct the browser to treat these links as scroll markers by using the scroll-target-group property on the list:

nav[aria-labelledby=toc-label] {
scroll-target-group: auto;
}

Now when a target section is scrolled into view, the browser will determine the active link in the group, which will match the :target-current selector and apply the active link styles to it:

a:target-current {
font-weight: bold;
text-decoration-thickness: 2px;
}

It’s literally as simple as that. Of course, you’d have additional styles in your stylesheet that make the table of contents stick to the top of the viewport or whatever as the user scrolls the page.

Here’s a video recording of the CSS Scrollspy in action:

I’ve discussed this feature in more depth and created a live example for you to tweak at in an article I just published ​on my blog​. You will need to view the live example in Chrome (140+) to see it working. I recommend you give the blog post a read when you can because in it I also discuss considerations for styling the active links accessibly, and I also mention how :target-current compares to :target when styling the document target element on page load.

Read the blog post

This feature is a nice little progressive enhancement that you can add to your designs… when it becomes ready for production. Unfortunately, as I wrote in the blog post, this feature is currently not entirely accessible.

When the browser determines the active link within the group, it is supposed to add aria-current=true to the active link—similar to what MDN does. A scroll marker, by definition, has a meaningful purpose: it lets the user know which part of content is currently being viewed.

What this means is that when you visually highlight an active link, you’re communicating meaningful information to the user. You must ensure that the same information is communicated to all users, including screen reader users, to ensure that you are not excluding anyone out. This is a baseline accessibility requirement.

WCAG Success Criterion ​1.3.1 Info and Relationships (Level A)​ states that information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.

Because the scroll-target-group property and the :target-current selector are designed to allow us to create JavaScript-free native HTML scroll markers, we should expect the browser to add and manage the necessary ARIA attribute(s) required for scroll markers to be inclusive. (After all, that’s the whole premise of this feature: to write a few lines of CSS and let the browser handle all the behavior for us.)

However, at the time of writing, Chrome (which is the only browser that currently supports this feature) doesn’t add aria-current=true to the active anchor yet. I filed ​an issue​ for this.

If you want to use this feature today, keep in mind that you will, for the time being, need to use JavaScript to add aria-current to the active anchor when its corresponding target scrolls into view, otherwise you risk an instant WCAG 1.3.1 violation.

I’ll personally wait till the issue is resolved and the feature becomes ready for production. I will update my blog post when the issue is resolved. When that happens, I’ll be among the first to add it as an enhancement in my CSS. ✌🏻 And that’s it for this issue!

In the next issue, I will be discussing another CSS feature—one that allows you to improve the usability and accessibility of your content. See you next week!



Level up your accessibility knowledge with the Practical Accessibility course!

I created a self-paced, get-right-down-to-it online video course for web designers and developers who want to start creating more accessible Web user interfaces and digital products today.

The course is now open for enrollment!

Subscribe elsewhere

RSS

Get my latest content in your favorite RSS reader. (What is RSS?)