How to Debug headroom.js Not Hiding or Showing Correctly: Complete Troubleshooting Guide

To fix headroom.js hide/show issues, verify you're using the UI library (headroom.js on npm) and not the Headroom AI repository, then check your HTML wrapper structure, ensure CSS classes like headroom--pinned are loaded, set appropriate offset and tolerance values, and enable debug: true to inspect scroll decisions in the browser console.

The chopratejas/headroom repository implements Headroom AI, a context-compression layer for LLM agents found in the core pipeline (README lines 57-71), which is completely unrelated to the scroll-based UI library. If your sticky header refuses to hide or show on scroll, you're working with the headroom.js frontend package. This guide applies debugging patterns from the Headroom AI codebase—such as event listener management in plugins/openclaw/hook-shim/handler.js and configuration validation in sdk/typescript/test/client.test.ts—to systematically resolve the UI library's visibility issues.

Step-by-Step Debugging Guide

Step 1: Confirm You're Using the Correct Library

The most common source of confusion stems from the repository name. chopratejas/headroom contains the headroom-ai SDK for compressing LLM context windows, not the animation library.

  • Headroom AI: Context-compression layer with TypeScript SDKs and OpenClaw plugins (this repository).
  • headroom.js: Frontend library that watches scroll events and toggles CSS classes (headroom--pinned, headroom--unpinned).

Verify your package.json contains "headroom.js". Search for "headroom.js" in your dependencies—it will not appear in the Headroom AI repo because this repo ships headroom-ai instead. If you're importing from the wrong package, the runtime will fall back to a no-op or throw resolution errors.

Step 2: Validate HTML Structure

The UI library requires a specific DOM structure: a wrapper element containing the header you want to toggle. Missing wrappers or multiple candidate elements prevent the script from attaching listeners correctly.

Inspect the OpenClaw plugin's DOM injection pattern in plugins/openclaw/hook-shim/handler.js to see how the repository programmatically inserts wrapper elements. Apply this pattern to your HTML:

<div class="headroom-wrapper">
  <header id="site-header">Your Header</header>
</div>

Ensure the selector you pass to the Headroom constructor matches exactly one element:

const header = document.querySelector("#site-header");

Step 3: Verify CSS Classes Are Loaded

If the JavaScript executes but visuals don't change, the library is likely working but the CSS is missing. The UI library toggles specific classes:

  • headroom--pinned (visible)
  • headroom--unpinned (hidden)
  • headroom--top (at scroll top)
  • headroom--not-top (scrolled down)

The chopratejas/headroom repository does not contain stylesheet assets for the UI library; the UI side must import the CSS from the headroom.js distribution:

/* From headroom.js distribution */
.headroom {
  will-change: transform;
  transition: transform 200ms linear;
}
.headroom--pinned {
  transform: translateY(0%);
}
.headroom--unpinned {
  transform: translateY(-100%);
}

Step 4: Inspect Configuration Options

Incorrect thresholds cause immediate hiding or never-hiding behaviors. The offset, tolerance, and classes options control when hide/show occurs.

The Headroom AI SDK in sdk/typescript/test/client.test.ts demonstrates how configuration objects structure options. Apply this pattern when constructing the UI instance:

const headroom = new Headroom(header, {
  offset: 100,        // Start hiding after 100px scroll
  tolerance: {        // Sensitivity to scroll direction changes
    up: 5,
    down: 0
  },
  classes: {
    pinned: "headroom--pinned",
    unpinned: "headroom--unpinned",
    top: "headroom--top",
    notTop: "headroom--not-top",
    initial: "headroom--initial"
  }
});

Step 5: Check Event Listeners

Open browser DevTools → ElementsEvent Listeners to confirm scroll events are bound to your element. If scroll events are blocked (e.g., by preventDefault on a parent), the library never receives updates.

The plugins/openclaw/hook-shim/handler.js file demonstrates proper addEventListener usage for hooking editor scroll events. Mirror this approach for your page:

// Ensure your scroller is correctly targeted (default is window)
const headroom = new Headroom(header, {
  scroller: window // or document.querySelector('.your-scroller')
});

Step 6: Look for Conflicting Scripts

Other libraries manipulating transform or position on the same element can interfere with the CSS transitions that headroom.js relies on.

The proxy/server.ts file illustrates how multiple middlewares compose without clobbering each other. Apply similar isolation to your UI scripts—ensure no other script overrides the header's transform properties while Headroom is active.

Step 7: Enable Debug Logging

Enable verbose logging to see exactly when the library decides to pin or unpin:

const headroom = new Headroom(header, {
  debug: true  // Prints decisions to console
});

This mirrors the logging pattern in sdk/typescript/test/client.test.ts where the SDK uses logger.info to surface runtime decisions. Check your console for messages like "not-top" or "unpinned" to verify the logic is triggering.

Step 8: Verify CDN and Build Loading

If importing from a CDN, ensure the script loads without 404 errors. A missing script results in silent failure where Headroom is undefined.

The repository's README demonstrates loading the Node SDK via npm install headroom-ai. For the UI library, use:

npm install headroom.js

And import from node_modules:

import Headroom from "headroom.js";
// or
const Headroom = require("headroom.js");

Step 9: Re-run the Minimal Example

Isolate the problem by testing the official minimal example from the headroom.js documentation. If the example works, the issue is in your integration, not the library.

While this repository doesn't contain UI examples, the test harness pattern in plugins/openclaw/test/engine.test.ts demonstrates how to spin up isolated test environments. Create a standalone HTML file with only Headroom and your header to eliminate external conflicts.

Step 10: Consult Official Documentation

The authoritative source for API specifics is the headroom.js repository at https://github.com/WickyNilliams/headroom.js. Check for version-specific pitfalls like the css: true requirement for older browsers.

Code Examples

Basic Initialization with Debug Mode

import Headroom from "headroom.js";

const header = document.querySelector("#site-header");

const headroom = new Headroom(header, {
  offset: 120,
  tolerance: { up: 5, down: 0 },
  classes: {
    pinned: "headroom--pinned",
    unpinned: "headroom--unpinned",
    top: "headroom--top",
    notTop: "headroom--not-top",
    initial: "headroom--initial"
  },
  debug: true  // Enable console logging
});

headroom.init();

Event-Based Logging

Log internal decisions using the event API, similar to the SDK's debug output:

headroom.on("top", () => console.log("Header reached the top"));
headroom.on("unpinned", () => console.log("Header hidden"));
headroom.on("pinned", () => console.log("Header shown"));

TypeScript Configuration

TypeScript example mirroring the SDK's type-safe patterns:

import Headroom from "headroom.js";

const header = document.getElementById("header") as HTMLElement;

const options: Headroom.Options = {
  offset: 80,
  tolerance: 5,
  debug: true,
};

new Headroom(header, options).init();

Key Files Reference

While debugging the UI library, these files from the Headroom AI repository demonstrate relevant architectural patterns:

  • plugins/openclaw/hook-shim/handler.js: Shows how to programmatically inject wrapper elements and attach scroll listeners—useful for understanding proper DOM structure and event binding.
  • sdk/typescript/test/client.test.ts: Demonstrates configuration object structures and debug logging patterns that translate directly to the UI library's options.
  • proxy/server.ts: Illustrates middleware composition without conflicts, applicable to keeping UI scripts isolated from other page scripts.
  • README.md: Provides the high-level architecture overview confirming that UI hiding/showing is not part of this repository's scope.

Summary

  • Verify the package: Ensure you installed headroom.js (UI library), not headroom-ai (LLM compression).
  • Check the HTML: Confirm you have a wrapper element around your header, as demonstrated in plugins/openclaw/hook-shim/handler.js.
  • Load the CSS: Import styles for headroom--pinned, headroom--unpinned, and other state classes.
  • Tune the config: Set offset and tolerance appropriate for your scroll container.
  • Enable debugging: Use debug: true to see pin/unpin decisions in the console.
  • Inspect events: Verify scroll listeners are attached in DevTools and not blocked by preventDefault.
  • Isolate conflicts: Ensure no other scripts override the header's transform properties.

Frequently Asked Questions

Why isn't my header hiding when I scroll down?

If the header remains visible, first check that the CSS classes are actually loaded—the library toggles headroom--unpinned to hide elements, but without CSS rules defining transform: translateY(-100%) or similar, the element won't move. Also verify your offset value isn't set to 0, which can cause immediate pinning, or that the scroller option correctly targets your scroll container (default is window).

What's the difference between Headroom AI and headroom.js?

Headroom AI (this repository at chopratejas/headroom) is a context-compression SDK for LLM agents with TypeScript clients and OpenClaw plugins. headroom.js is a completely separate frontend library that hides/shows page headers based on scroll direction. They share a name but have no code relationship—one handles AI context windows, the other handles UI animations.

How do I see exactly when headroom.js decides to hide or show the header?

Pass debug: true in the configuration options: new Headroom(element, { debug: true }).init(). This prints internal state changes to the browser console, showing exactly when the library switches between "pinned" and "unpinned" states. You can also attach event listeners for specific states: headroom.on('unpinned', () => console.log('hiding')).

Can I use headroom.js with a custom scroll container instead of the window?

Yes, use the scroller option to specify a custom element: new Headroom(header, { scroller: document.querySelector('.my-scroller') }). Ensure this element actually has scrollable overflow content and that you're not accidentally attaching events to a static parent. Check the event listeners in DevTools to confirm the scroll event is bound to your intended container.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →