Rejouice — loading0

Condensing sticky nav

A navigation bar that starts tall and open, then condenses to a compact strip once the user scrolls past the hero.

Grade: ARuntimes: cssStatus: code-only

Documented, no live demo yet — the code below is complete.

Intent

The hero deserves an unobstructed first screen, but navigation still has to be reachable from anywhere. Condensing rather than hiding keeps the wayfinding available at all times while returning most of the vertical budget to the content.

Implementation

The sentinel is a zero-height element at the top of the flow; when it leaves the viewport, the nav condenses.

Dependencies: none

html-css-js
<div data-nav-sentinel aria-hidden="true"></div>
<header class="nav" data-condensed="false">
  <a class="nav__mark" href="/">Studio</a>
  <nav class="nav__links" aria-label="Primary">
    <a href="#work">Work</a>
    <a href="#studio">Studio</a>
    <a href="#contact">Contact</a>
  </nav>
</header>

<style>
[data-nav-sentinel] { height: 1px; }

.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav, 8000);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  height: 6rem;
  padding-inline: var(--gutter);
  color: var(--ink);
  background: var(--bg);
  transition:
    height var(--dur-micro, 0.4s) var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
    border-color var(--dur-micro, 0.4s) linear;
  border-block-end: 1px solid transparent;
}

.nav[data-condensed="true"] {
  height: 3.5rem;
  border-block-end-color: var(--line);
}

/* Anchor targets clear the condensed bar. */
:target,
[id] { scroll-margin-block-start: 4.5rem; }

@media (prefers-reduced-motion: reduce) {
  .nav { transition: none; }
}
</style>

<script type="module">
const sentinel = document.querySelector("[data-nav-sentinel]");
const nav = document.querySelector(".nav");

if (sentinel && nav) {
  new IntersectionObserver(([entry]) => {
    nav.dataset.condensed = String(!entry.isIntersecting);
  }).observe(sentinel);
}
</script>

Use it when

  • Long marketing pages, single-page sites, case studies.

Avoid it when

  • Nav that hides on scroll-down and reappears on scroll-up — that pattern is a different, more disorienting trade and should not be mixed with this one.
  • Very short pages, where the condensed state never gets used.

Accessibility

  • A sticky header must not consume more than about 20% of a short viewport — check at 400px height in landscape.
  • Add `scroll-margin-block-start` equal to the condensed height on anchor targets so in-page links do not land underneath the bar.
  • The condensed state must keep a visible focus ring and must not reduce touch targets below 44px.

Performance

  • Observe a sentinel with IntersectionObserver rather than listening to scroll — no per-frame work at all.
  • Transition `height` and `padding` only on a small element; avoid transitioning `backdrop-filter`.

Knobs

trigger
Distance scrolled before condensing. Default: hero height
condensed height
Height of the compact state. Default: 3.5rem

Composition

Instruction for Claude Code

Build a condensing sticky nav: a position-sticky header at 6rem that drops to 3.5rem with a bottom hairline when a data-condensed attribute flips to true. Drive the flip from an IntersectionObserver on a 1px sentinel at the top of the document — no scroll listener. Inherit --ink and --bg so the bar survives page inversion, add scroll-margin-block-start to anchor targets so in-page links clear the bar, keep touch targets at 44px in the condensed state, and disable the transition under prefers-reduced-motion. Do not hide the nav on scroll-down.