Scroll and navigation selectors

These selectors replace the small scripts nearly every site copies: highlight the link for this page, shrink the header after scrolling, hide it on the way down, show shadows on scrollable areas, highlight the section being read. Like every css-pp selector, each one compiles to an attribute selector and works inside :has().

Selector Matches Native CSS in 2026
:current A link to this page, or to a parent section of it :local-link is specified but not in any browser
:current(exact) A link to exactly this page No
:scrolled, :scrolled(80px) The page or a container is scrolled down at least that far (default 1px) Chromium only
:scrolling-down, :scrolling-up The last scroll movement went down or up Chromium only
:scroll-start, :scroll-end A scroll container is at its start or end Chromium only
:target-in-view A #link whose section is the one being read (scrollspy) No

:current — the active link#

CSS
nav a:current { font-weight: 700; border-bottom: 2px solid; }

:current compares the link's path with the page's path, ignoring query strings, hashes, trailing slashes and index.* file names. It also matches links to a parent section, so a "Docs" link to /docs/ stays highlighted on /docs/nesting/. The home link / only matches on the home page. Use :current(exact) to match only the page itself. In-page #anchor links never match.

:scrolled — after the page moves#

CSS
header { height: 80px; transition: height .2s, box-shadow .2s; }
html:scrolled(80px) header { height: 56px; box-shadow: 0 2px 12px #0002; }

On html or body it measures the page; on any other element, that element's own scroll position.

:scrolling-down and :scrolling-up#

CSS
header { position: sticky; top: 0; transition: translate .25s; }
html:scrolling-down header { translate: 0 -100%; }   /* hide on the way down, back on the way up */

Movements under 5px are ignored, so the header doesn't flicker. At the very top neither state is on.

:scroll-start and :scroll-end — scroll shadows and carousels#

CSS
.list { overflow: auto; max-height: 20rem; position: relative; }
.list:not(:scroll-end) { box-shadow: inset 0 -16px 12px -12px #0003; }

.carousel:scroll-start .prev,
.carousel:scroll-end .next { opacity: .3; pointer-events: none; }

css-pp uses whichever axis actually overflows, so it works for vertical lists and horizontal carousels alike. A container with nothing to scroll is both at its start and its end.

:target-in-view — scrollspy#

CSS
.toc a:target-in-view { color: var(--accent); font-weight: 600; }

Among the #links the selector matches, the one whose target section has most recently crossed the top third of the screen is on. Only one link per group is active at a time, and it updates as you scroll and when the URL hash changes.