A floating laptop on a magician's stage with a website header disappearing behind a velvet curtain

Peek: The Disappearing Header Act Your Site Has Been Missing

The Orange Cat
The Orange Cat

You have probably seen it on every modern mobile site: scroll down and the header slides away to give you more reading room, scroll back up and it reappears instantly, ready when you need it. This pattern has become a staple of good UX, especially on smaller screens where every pixel counts. Peek is a tiny vanilla JavaScript library that delivers exactly this behavior. It watches scroll direction, distinguishes between intentional navigation and idle thumb-twitches, and toggles CSS classes on your header element accordingly. The best part? The entire library clocks in at roughly 4KB unminified with absolutely zero dependencies.

Why Peek Stands Out

Peek is laser-focused on one job and does it well. Here is what it brings to the table:

  • Scroll intent detection -- It does not just track direction. It uses a configurable tolerance threshold to filter out minor scroll jitter so your header does not flicker on every tiny movement.
  • RequestAnimationFrame powered -- All DOM updates run inside requestAnimationFrame, which means smooth 60fps transitions without janky reflows.
  • Passive event listeners -- Scroll listeners are registered as passive, telling the browser it is safe to scroll immediately without waiting for JavaScript. This eliminates scroll jank on mobile.
  • Offset awareness -- You can define a pixel offset from the top of the page. The header stays pinned while the user is within that zone and only starts its hide/show routine once they scroll past it.
  • CSS-class based -- Peek does not touch inline styles or force animations. It applies and removes classes, leaving full creative control to your stylesheets.
  • Cleanup method -- A single destroy() call removes all listeners and classes, making it safe for single-page apps and dynamic layouts.

Getting Peek on Stage

Since Peek is distributed through GitHub rather than npm, you grab it directly from the repository. Download peek.js and peek.css from the repo and include them in your project:

<script src="path/to/peek.js"></script>

If you are working in a module-based build system, you can copy the source into your project and import it:

// Copy peek.js into your project, then import
import Peek from "./lib/peek";

The CSS file provides sensible defaults for the transition, but you can write your own styles from scratch if you prefer.

Your First Vanishing Act

Setting Up the Header

The simplest way to get started is to call Peek with a CSS selector pointing at your header element:

const peek = Peek(".header-main");

That is it. With zero configuration, Peek starts watching the scroll position and toggles four CSS classes on your header:

  • main-header--pinned when the header should be visible
  • main-header--unpinned when it should be hidden
  • main-header--top when the page is at or near the top
  • main-header--not-top when the user has scrolled past the offset threshold

Adding the CSS Magic

Peek handles the logic, but you need CSS to make the header actually move. Here is a minimal setup using CSS transforms for hardware-accelerated animation:

.header-main {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  transition: transform 0.3s ease;
  will-change: transform;
}

.main-header--unpinned {
  transform: translateY(-100%);
}

.main-header--pinned {
  transform: translateY(0);
}

Using transform instead of changing top or margin is deliberate. Transforms are composited on the GPU and do not trigger layout recalculations, which keeps your scroll performance pristine.

Customizing the Defaults

If the default class names clash with your existing styles, or you want to tweak the scroll sensitivity, pass an options object:

const peek = Peek(".header-main", {
  offset: 150,
  tolerance: 10,
  classes: {
    pinned: "header-visible",
    unpinned: "header-hidden",
    top: "at-top",
    notTop: "scrolled",
  },
});

The offset tells Peek how many pixels the user must scroll before the hide/show behavior kicks in. The tolerance sets how many pixels of directional scroll are needed before a state change fires. Bumping tolerance up to 10 or 15 prevents the header from reacting to accidental micro-scrolls, which feels much more polished on touch devices.

Leveling Up the Trick

Mobile-Only Headers

On desktop, you might want the header to always stay visible since screen real estate is not as precious. A common pattern is to initialize Peek only on smaller viewports and destroy it when the window grows:

let peek: ReturnType<typeof Peek> | null = null;

function initPeekOnMobile(): void {
  if (window.innerWidth < 768 && !peek) {
    peek = Peek(".header-main");
  } else if (window.innerWidth >= 768 && peek) {
    peek.destroy();
    peek = null;
  }
}

initPeekOnMobile();
window.addEventListener("resize", initPeekOnMobile);

The destroy() method cleanly removes all event listeners and strips away every class Peek added, so your header returns to its natural state without leftover artifacts.

Multiple Headers on One Page

Some layouts have a primary navigation bar and a secondary toolbar or a sticky sub-nav. Since Peek targets a CSS selector, you can run multiple instances side by side with different configurations:

const mainNav = Peek(".header-main", {
  offset: 100,
  tolerance: 5,
  classes: {
    pinned: "main-nav--visible",
    unpinned: "main-nav--hidden",
    top: "main-nav--top",
    notTop: "main-nav--scrolled",
  },
});

const subNav = Peek(".sub-nav", {
  offset: 200,
  tolerance: 15,
  classes: {
    pinned: "sub-nav--visible",
    unpinned: "sub-nav--hidden",
    top: "sub-nav--top",
    notTop: "sub-nav--scrolled",
  },
});

Giving the sub-nav a higher offset and tolerance creates a layered effect where the main header hides first and the sub-nav follows shortly after, which feels natural and intentional.

Pairing with Scroll-Triggered Styles

The top and notTop classes are not just for the hide/show effect. They are perfect for changing the header's appearance as the user scrolls. A transparent hero header that turns opaque after scrolling is a one-liner in CSS:

.header-main.main-header--top {
  background: transparent;
  box-shadow: none;
}

.header-main.main-header--not-top {
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: background 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
}

No JavaScript needed for the visual transition. Peek sets the right class at the right time, and CSS handles the rest.

How the Magic Works Under the Hood

Peek's algorithm is refreshingly simple, which is exactly why it works so well. On every animation frame it checks three things:

  1. Are we at the top? If the scroll position is within the offset threshold, the header gets pinned with the top class. No further checks needed.
  2. Which direction are we scrolling? It compares the current scroll position with the last recorded position to determine direction.
  3. Has the user scrolled far enough? The tolerance check ensures that only movements exceeding the threshold trigger a state change. This single guard eliminates the flickering that plagues naive scroll-direction implementations.

The entire state machine fits in roughly 100 lines of code. There are no virtual DOMs, no framework bridges, no polyfills. Just requestAnimationFrame, classList, and passive addEventListener -- three browser APIs that have been stable and performant for years.

The 4KB Contender vs the 23KB Champion

If this concept sounds familiar, you have probably heard of Headroom.js, the library that popularized scroll-aware headers. Headroom weighs in at about 23KB unpacked and has not seen a commit since late 2023. Peek offers the same core behavior at roughly one-sixth the size with a more modern codebase that was actively updated in late 2025. If you do not need Headroom's plugin ecosystem or jQuery integration, Peek is a compelling swap that shaves kilobytes off your bundle and gives you cleaner, more readable source code to fork if you ever need to customize it.

Take a Bow

Peek is the kind of library that makes you appreciate focused engineering. It solves one problem, solves it well, and gets out of your way in under 4KB. Whether you are building a content-heavy blog, a mobile-first marketing page, or a documentation site where reading space matters, Peek gives your headers the intelligence to know when to show up and when to disappear. The API is a single function call, the cleanup is a single method, and the performance is as smooth as your browser can render. Sometimes the best tool for the job is the smallest one in the drawer.