# :current, :scrolled, :scrolling-up, :scroll-end and scrollspy selectors

> Highlight the active nav link with :current, shrink or hide headers with :scrolled and :scrolling-down, scroll shadows with :scroll-end, and scrollspy in CSS.

Source: https://csspp.dev/docs/scroll-navigation/


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 {#current}

```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 {#scrolled}

```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 {#direction}

```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 {#scroll-ends}

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