npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

zeebra

v0.1.1

Published

A performant z-index management library with virtual z-stack recycling

Readme

Zeebra 🦓

A performant z-index management library with virtual z-stack recycling. Zeebra efficiently manages z-indexes for floating elements (like draggable dialogs) by maintaining a virtual stack and recycling z-index values within a configurable range.

Features

  • 🚀 Super performant - Uses CSS variables and batched updates to avoid component re-renders
  • ♻️ Z-index recycling - Automatically recycles z-indexes within your range
  • 🎯 Zero wrapper required - Works directly with DOM elements
  • ⚛️ React support - Optional wrapper component and hooks for React developers
  • 🔧 Auto-detection - Automatically detects elements with data-zeebra attribute
  • 📦 TypeScript - Full TypeScript support
  • 🧪 Well tested - Comprehensive unit and integration tests

Installation

npm install zeebra

Quick Start

Vanilla JavaScript

import zeebra from 'zeebra';

const element = document.getElementById('my-dialog');

// Register the element
zeebra.register(element);

// Lift to top
zeebra.lift(element);

// Lower to bottom
zeebra.lower(element);

React

import { Zeebra } from 'zeebra/react';

function MyDialog() {
  return (
    <Zeebra>
      <div>My draggable dialog</div>
    </Zeebra>
  );
}

Table of Contents

Core Concepts

Virtual Z-Stack

Zeebra maintains a virtual z-stack in memory. Each registered element gets a virtual position (0, 1, 2, ...). The actual z-index is calculated as:

z-index = --zeebra-start + virtualPosition

Z-Index Recycling

When you lift an element, it moves to the end of the virtual stack (highest z-index). Other elements maintain their relative order but get new z-index values. This recycling ensures you never leave your configured range.

Example:

  • Elements: [A: 5000, B: 5001, C: 5002]
  • lift(A) → [B: 5000, C: 5001, A: 5002]

CSS Variables

Zeebra uses CSS custom properties (--zeebra-z-index) to update z-indexes. This approach:

  • Avoids triggering React re-renders
  • Allows CSS to handle the actual z-index application
  • Enables batched updates via requestAnimationFrame

API Reference

Core Methods

register(element: HTMLElement): void

Register an element in the z-stack. The element will be assigned the next available z-index.

const dialog = document.getElementById('dialog');
zeebra.register(dialog);

unregister(element: HTMLElement): boolean

Remove an element from the z-stack. Returns true if the element was registered, false otherwise.

zeebra.unregister(dialog);

lift(element: HTMLElement, sync?: boolean): void

Raise an element to the top (highest z-index). Auto-registers if not already registered.

  • sync (optional): If true, updates CSS immediately instead of batching. Default: false
// Async update (batched)
zeebra.lift(dialog);

// Sync update (immediate)
zeebra.lift(dialog, true);

lower(element: HTMLElement, sync?: boolean): void

Lower an element to the bottom (lowest z-index). Auto-registers if not already registered.

zeebra.lower(dialog);
zeebra.lower(dialog, true); // Immediate update

bringToFront(element: HTMLElement, sync?: boolean): void

Alias for lift(). Brings element to the front.

zeebra.bringToFront(dialog);

sendToBack(element: HTMLElement, sync?: boolean): void

Alias for lower(). Sends element to the back.

zeebra.sendToBack(dialog);

getZIndex(element: HTMLElement): number | null

Get the current z-index value for an element (synchronously calculated). Returns null if the element is not registered.

const zIndex = zeebra.getZIndex(dialog);
console.log(zIndex); // 5000, 5001, etc.

getVirtualPosition(element: HTMLElement): number | null

Get the virtual position of an element in the z-stack. Returns null if not registered.

const position = zeebra.getVirtualPosition(dialog);
console.log(position); // 0, 1, 2, etc.

getTrackedElements(): HTMLElement[]

Get all currently tracked elements in order (lowest to highest z-index).

const elements = zeebra.getTrackedElements();
console.log(elements.length); // Number of tracked elements

getNextZIndex(): number

Get the next available z-index value (useful for manual z-index assignment).

const nextZIndex = zeebra.getNextZIndex();
console.log(nextZIndex); // 5000 + number of tracked elements

isRegistered(element: HTMLElement): boolean

Check if an element is registered in the z-stack.

if (zeebra.isRegistered(dialog)) {
  // Element is tracked
}

React API

<Zeebra> Component

Wrapper component that automatically manages z-index for its children.

import { Zeebra } from 'zeebra/react';

function MyDialog() {
  return (
    <Zeebra>
      <div>My dialog content</div>
    </Zeebra>
  );
}

Props

  • children: React.ReactNode - Child elements to wrap
  • autoRegister?: boolean - Auto-register on mount (default: true)
  • onRegister?: (element: HTMLElement) => void - Callback when element is registered
  • onUnregister?: (element: HTMLElement) => void - Callback when element is unregistered
  • All standard HTML div props are supported

Ref API

import { Zeebra, ZeebraRef } from 'zeebra/react';

function MyDialog() {
  const zeebraRef = useRef<ZeebraRef>(null);

  const handleClick = () => {
    zeebraRef.current?.lift();
  };

  return (
    <Zeebra ref={zeebraRef}>
      <button onClick={handleClick}>Bring to Front</button>
    </Zeebra>
  );
}

Ref Methods:

  • lift() - Lift element to top
  • lower() - Lower element to bottom
  • bringToFront() - Alias for lift
  • sendToBack() - Alias for lower
  • getElement() - Get the underlying DOM element

useZeebra() Hook

Hook for programmatic z-index control with your own refs.

import { useZeebra } from 'zeebra/react';

function MyDialog() {
  const elementRef = useRef<HTMLDivElement>(null);
  const { lift, lower, bringToFront, sendToBack } = useZeebra(elementRef);

  return (
    <div ref={elementRef}>
      <button onClick={lift}>Bring to Front</button>
      <button onClick={lower}>Send to Back</button>
    </div>
  );
}

Returns:

  • lift: () => void - Lift element to top
  • lower: () => void - Lower element to bottom
  • bringToFront: () => void - Alias for lift
  • sendToBack: () => void - Alias for lower
  • register: () => void - Manually register element
  • unregister: () => void - Manually unregister element

Configuration

CSS Variable: --zeebra-start

Set the starting z-index value using a CSS variable:

:root {
  --zeebra-start: 5000; /* Default is 5000 */
}

Elements will use z-indexes starting from this value and incrementing based on their virtual position.

Auto-Detection

Elements with the data-zeebra attribute are automatically registered:

<div data-zeebra>
  This element is automatically tracked
</div>

Auto-detection runs on:

  • DOM ready
  • Dynamically added elements (via MutationObserver)

Advanced Usage

Synchronous Updates

By default, z-index updates are batched using requestAnimationFrame for performance. When you need the z-index immediately (e.g., for a third-party library), use sync mode:

// Sync mode - updates immediately
zeebra.lift(element, true);
const zIndex = zeebra.getZIndex(element); // Available immediately

Manual Z-Index Application

If you need to apply z-index to a parent container (like with react-rnd):

zeebra.lift(element, true);
const zIndex = zeebra.getZIndex(element);

// Apply to parent container
const container = element.parentElement;
if (container && zIndex !== null) {
  container.style.zIndex = String(zIndex);
}

Multiple Z-Stack Instances

The library uses a global instance by default. For multiple independent z-stacks, you can create separate instances:

import { VirtualZStack } from 'zeebra/virtual-zstack';

const customStack = new VirtualZStack();
customStack.register(element);

Event-Driven Updates

Listen to z-index changes by polling or using a custom event system:

// Poll for changes
setInterval(() => {
  const tracked = zeebra.getTrackedElements();
  tracked.forEach(el => {
    const zIndex = zeebra.getZIndex(el);
    // Update UI or sync with external system
  });
}, 100);

Performance

Batched Updates

Zeebra batches CSS updates using requestAnimationFrame, ensuring:

  • All updates happen in a single frame
  • Minimal DOM manipulation
  • No layout thrashing

CSS Variables

Using CSS variables instead of direct style updates:

  • Avoids React re-renders
  • Leverages browser's CSS engine
  • Enables CSS-based styling

WeakMap Storage

Element references are stored in WeakMaps, allowing:

  • Automatic garbage collection
  • No memory leaks
  • Efficient lookups

Testing

The library includes comprehensive unit and integration tests.

Run Tests

# Run all tests
npm test

# Watch mode
npm run test:watch

# With coverage
npm run test:coverage

# CI mode (JUnit XML output)
npm run test:ci

Test Structure

  • Unit Tests: Core logic, utilities, CSS manager
  • Integration Tests: Full workflows, DOM interactions, React components

See tests/README.md for details.

Examples

Draggable Windows

import zeebra from 'zeebra';

// Create draggable window
const window = document.createElement('div');
window.className = 'window';
document.body.appendChild(window);

// Register with zeebra
zeebra.register(window);

// Lift on click
window.addEventListener('click', () => {
  zeebra.lift(window);
});

// Lift on drag start
window.addEventListener('mousedown', (e) => {
  if (e.target.classList.contains('window-header')) {
    zeebra.lift(window);
    // Start drag...
  }
});

React Draggable Dialog

import { Zeebra } from 'zeebra/react';
import { Rnd } from 'react-rnd';

function DraggableDialog() {
  const zeebraRef = useRef(null);
  const windowRef = useRef(null);

  return (
    <Zeebra ref={zeebraRef}>
      <Rnd
        onDragStart={() => {
          zeebraRef.current?.lift();
        }}
      >
        <div ref={windowRef} className="dialog">
          Dialog content
        </div>
      </Rnd>
    </Zeebra>
  );
}

Modal Stack Management

const modals = [];

function openModal(content) {
  const modal = document.createElement('div');
  modal.className = 'modal';
  modal.innerHTML = content;
  document.body.appendChild(modal);
  
  zeebra.register(modal);
  modals.push(modal);
  
  // Automatically bring new modal to front
  zeebra.lift(modal);
}

function closeModal(modal) {
  zeebra.unregister(modal);
  modal.remove();
  modals = modals.filter(m => m !== modal);
}

Browser Support

  • Modern browsers with CSS custom properties support
  • IE11 not supported (requires CSS variables)

TypeScript

Full TypeScript definitions are included:

import zeebra, { HTMLElement } from 'zeebra';

const element: HTMLElement = document.getElementById('dialog')!;
zeebra.register(element);

Contributing

Contributions are welcome! Please see our contributing guidelines.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

MIT

Changelog

See CHANGELOG.md for version history.