:target-current

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 :target-current CSS pseudo-class selects active scroll markers, meaning it’s only valid when used with the ::scroll-marker pseudo-element, like this:

/* All scroll markers */
::scroll-marker {
  content: "";
  border: 1px solid black;
}

/* All active scroll markers */
::scroll-marker:target-current {
  background: black;
}

Scroll markers give users an extra way to navigate overflow content, making them perfect for carousels, tabs, layouts with scroll-snapping functionality, and various other scroll-based components. They’re also completely accessible right out of the box — they can be navigated using a keyboard or assistive technology, and screen readers describe them fairly well too.

Here’s a carousel example:

Syntax

:target-current {
  /* ... */
}

Basic usage

To select all active scroll markers:

::scroll-marker:target-current {
  background: black;
}

Although, because :target-current only applies to scroll markers anyway, the following snippet would be less redundant:

:target-current {
  background: black;
}

However, to actually create scroll markers, there are a few requirements. First, the scroll targets must exist within a scroll container whose overflow property is set to anything other than visible. Second, its scroll-marker-group property must be set to either before or after. And finally, the scroll markers must have valid content properties. While not a fully-functional example, here’s what these requirements (along with :target-current) look like:

<ul class="scroll-container">
  <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>
</ul>
.scroll-container {
  overflow-x: hidden; /* Anything but visible */
  scroll-marker-group: after; /* before | after */

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

  .scroll-target::scroll-marker:target-current {
    /* Active scroll marker styles */
  }
}

To better understand how scroll markers work overall, take a look at the carousel demo below. You’ll see that scroll-marker-group is set to after — this is because the scroll markers appear after the content visually, and therefore should do so in the tab order as well. We then select the ::scroll-marker-group pseudo-element, using anchor positioning to position the (group of) scroll markers relative to the scroll container, as well as flexbox to organize the scroll markers within. After that, it’s simply a matter of styling the scroll markers by selecting ::scroll-marker and styling the active scroll marker with :target-current.

<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;
  }
}

Examples: Pagination and tabs

By leveraging the capabilities of the content property, we can give the scroll markers some content. The scroll markers in the example above don’t have any content — they’re purely visual and so the content property is left blank (content: ""), but the example below makes use of the counter() function to create what’s essentially a paginated scroll container where the scroll markers are numbered and the active scroll marker is emboldened (they also appear underlined because browsers, by default, style them as links).

Here’s a similar example with tabs, where the scroll markers have worded labels and the active scroll marker has a border-bottom:

Browser support

We can detect browser support for it, if needed:

@supports selector(:target-current) {
  /* :target-current supported */
}

@supports not selector(:target-current) {
  /* :target-current not supported */
}

The same thing in JavaScript:

if (CSS.supports("selector(:target-current)")) {
  /* :target-current supported */
}

if (!CSS.supports("selector(:target-current)")) {
  /* :target-current not supported */
}

Specification

The :target-current pseudo-class 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