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