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

> Fix headroom.js hide/show bugs. Troubleshoot UI library conflicts, HTML wrappers, CSS classes, offset, tolerance, and enable debug mode for console insights.

- Repository: [Tejas Chopra/headroom](https://github.com/chopratejas/headroom)
- Tags: how-to-guide
- Published: 2026-06-11

---

**To fix headroom.js hide/show issues, verify you're using the UI library ([`headroom.js`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/plugins/openclaw/hook-shim/handler.js) and configuration validation in [`sdk/typescript/test/client.test.ts`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/plugins/openclaw/hook-shim/handler.js) to see how the repository programmatically inserts wrapper elements. Apply this pattern to your HTML:

```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:

```javascript
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`](https://github.com/chopratejas/headroom/blob/main/headroom.js) distribution:

```css
/* 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`](https://github.com/chopratejas/headroom/blob/main/sdk/typescript/test/client.test.ts) demonstrates how configuration objects structure options. Apply this pattern when constructing the UI instance:

```javascript
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 → **Elements** → **Event 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`](https://github.com/chopratejas/headroom/blob/main/plugins/openclaw/hook-shim/handler.js) file demonstrates proper `addEventListener` usage for hooking editor scroll events. Mirror this approach for your page:

```javascript
// 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`](https://github.com/chopratejas/headroom/blob/main/headroom.js) relies on.

The [`proxy/server.ts`](https://github.com/chopratejas/headroom/blob/main/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:

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

```

This mirrors the logging pattern in [`sdk/typescript/test/client.test.ts`](https://github.com/chopratejas/headroom/blob/main/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:

```bash
npm install headroom.js

```

And import from `node_modules`:

```javascript
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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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

```javascript
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:

```javascript
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:

```typescript
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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/proxy/server.ts)**: Illustrates middleware composition without conflicts, applicable to keeping UI scripts isolated from other page scripts.
- **[`README.md`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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.