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

sidebarius

v1.0.16

Published

Sidebarius is a lightweight, zero-dependency JavaScript tool that makes sidebars sticky and scrollable.

Readme

Sidebarius

NPM License NPM version NPM downloads (total) NPM Downloads (montly)

Sidebarius is a lightweight, zero-dependency JavaScript tool that makes sidebars sticky and scrollable.

Demo 👈

Table of Contents


Installation

Install the package via npm:

npm install sidebarius

Usage

Before use

  1. Container cannot use the CSS property padding (must be 0).
  2. ContainerInner cannot use the CSS property margin (must be 0).

Note: ContainerInner always relies on the Container's width and updates to match it whenever that width changes. How the Container's width is set doesn't matter — only that it is explicitly defined; that lets ContainerInner switch to position: absolute/fixed without affecting the Container's own width.


Examples

<main>
  <aside id="container">
    <div id="container_inner"><!-- Sidebar content --></div>
  </aside>
  <section><!-- Section content --></section>
</main>

<!-- Import globally, or use ES Modules as shown below -->
<!-- <script src="./sidebarius.iife.min.js"></script> -->

<script type="module">
  import Sidebarius from "./sidebarius.esm.min.js"; // Or use the global import (see commented line above)

  const sidebarius = new Sidebarius(
    document.getElementById("container"),
    document.getElementById("container_inner"),
    16, // spaceBottom (optional, default is 0)
    16, // spaceTop (optional, default is 0)
  );

  sidebarius.start();
</script>

Latest release

import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import Sidebarius, { type Callback, type Direction, type State, type Strategy } from "sidebarius";

type SidebarContainerProps = React.HTMLAttributes<HTMLDivElement> & {
  style?: Omit<React.CSSProperties, "padding">;
};

type SidebarContainerInnerProps = React.HTMLAttributes<HTMLDivElement> & {
  style?: Omit<React.CSSProperties, "margin">;
};

type SidebarProps = React.PropsWithChildren<{
  container?: SidebarContainerProps;
  containerInner?: SidebarContainerInnerProps;
}>;

const Sidebar: React.FC<SidebarProps> = ({ container = {}, containerInner = {}, children }) => {
  const containerRef = React.useRef<HTMLElement | null>(null);
  const containerInnerRef = React.useRef<HTMLDivElement | null>(null);

  React.useEffect(() => {
    if (!containerRef.current || !containerInnerRef.current) return;

    const callback: Callback = (state: State, direction: Direction, strategy: Strategy) => {
      console.log("STATE: " + ["None", "ContainerBottom", "ColliderTop", "ColliderBottom", "TranslateY", "Rest"][state]);
      console.log("DIRECTION: " + ["None", "Down", "Up"][direction]);
      console.log("STRATEGY: " + ["None", "Both", "Top"][strategy]);
    };

    const sidebarius = new Sidebarius(
      containerRef.current,
      containerInnerRef.current,
      16, // spaceBottom
      16, // spaceTop
      callback, // The callback function will be called every time the sidebar state/direction/strategy changes
    );

    sidebarius.start();

    return () => {
      sidebarius.stop();
    };
  }, []);

  return (
    <aside
      ref={containerRef}
      {...container}
    >
      <div
        ref={containerInnerRef}
        {...containerInner}
      >
        {children}
      </div>
    </aside>
  );
};

const App: React.FC = () => {
  return (
    <div style={{ textAlign: "center" }}>
      <header style={{ paddingBlock: 24 }}>Header Content</header>
      <div style={{ display: "flex", gap: 10, maxWidth: 1200, margin: "auto" }}>
        <Sidebar
          container={{ style: { width: 240, flexShrink: 0 } }}
          containerInner={{ style: { border: "1px solid gray", padding: 10, boxSizing: "border-box" } }}
        >
          <h3>Sidebar</h3>
          <p>
            Read <a href="https://github.com/phenomenonus/sidebarius/blob/main/README.md#usage">Usage</a> section in
            README.md for more details
          </p>
          {new Array(7).fill(null).map((_, i) => (
            <h2
              key={i}
              style={{ marginTop: 200 }}
            >
              Sidebar content {i}
            </h2>
          ))}
        </Sidebar>
        <div style={{ flexGrow: 1, padding: 10, border: "1px solid gray" }}>
          {"SIDEBARIUS".split("").map((letter, index) => (
            <div
              key={index}
              style={{ marginBlock: 128, fontSize: 96 }}
            >
              {letter}
            </div>
          ))}
        </div>
      </div>
      <footer style={{ textAlign: "center", paddingBlock: 24, height: 2000 }}>Footer Content</footer>
    </div>
  );
};

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);
import React from "react";
import Sidebarius from "sidebarius";

const App: React.FC = () => {
  const containerRef = React.useRef<HTMLElement | null>(null);
  const containerInnerRef = React.useRef<HTMLDivElement | null>(null);

  React.useEffect(() => {
    if (!containerRef.current || !containerInnerRef.current) return;

    const sidebarius = new Sidebarius(
      containerRef.current,
      containerInnerRef.current,
      16, // spaceBottom
      16, // spaceTop
    );

    sidebarius.start();

    return () => {
      sidebarius.stop();
    };
  }, []);

  return (
    <div className="text-center">
      <header className="py-6">Header Content</header>

      <div
        className="grid gap-2.5 max-w-[1200px] mx-auto"
        style={{ gridTemplateColumns: "240px 1fr" }}
      >
        <aside ref={containerRef}>
          <div
            ref={containerInnerRef}
            className="border border-gray-400 p-2"
          >
            <h3>Sidebar</h3>
            <p>
              Read{" "}
              <a
                href="https://github.com/phenomenonus/sidebarius/blob/main/README.md#usage"
                className="text-blue-600 underline"
              >
                Usage
              </a>{" "}
              section in README.md for more details
            </p>

            {new Array(8).fill(null).map((_, i) => (
              <h2
                key={i}
                className="mt-[200px]"
              >
                Sidebar content {i}
              </h2>
            ))}
          </div>
        </aside>

        <main className="flex-grow p-2 border border-gray-400">
          {"SIDEBARIUS".split("").map((letter, index) => (
            <div
              key={index}
              className="my-[128px] text-[96px]"
            >
              {letter}
            </div>
          ))}
        </main>
      </div>

      <footer className="text-center py-6 h-[2000px]">Footer Content</footer>
    </div>
  );
};

Concept

Preview

Viewport concepts | Viewport | Collider


Strategy

Strategy


API

Constructor Parameters

constructor(
  container: HTMLElement,      // See Concept section below
  containerInner: HTMLElement, // See Concept section below
  spaceBottom?: number,        // by default 0
  spaceTop?: number,           // by default 0
  callback?: Function          // See Callback section below
);

Examples

// Minimal
new Sidebarius(container, containerInner);

// Set spaces
new Sidebarius(container, containerInner, 16, 8);

// With Callback
new Sidebarius(container, containerInner, 0, 0, (state, direction, strategy) => {
  console.log(state, direction, strategy);
});

| Parameter | Type | Description | | ---------------- | ----------- | ----------------------------------------------------------------------------------- | | container | HTMLElement | The parent element (Container) that holds the sticky element. | | containerInner | HTMLElement | The element endowed with stickiness and scrolling abilities relative to its parent. | | spaceBottom | number | The space between the bottom of the viewport and the visible area. Default is 0. | | spaceTop | number | The space between the top of the viewport and the visible area. Default is 0. | | callback | Function | A function called before changes to the ContainerInner occur. See Callback |


Methods

| Method | Description | | ---------------------------------- | ----------------------------------------------------------- | | start() | Starts the sticky scrolling behavior. | | stop() | Stops the sticky scrolling behavior. | | setSpaces(spaceBottom, spaceTop) | Sets the distance to the collider and triggers a re-render. |


Types

State

Defines the positioning states of the ContainerInner.

| Value | Description | | ----- | -------------------------------------------------------------------------------------------------- | | 0 | None: Default behavior of ContainerInner. | | 1 | ContainerBottom: ContainerInner is affixed to the bottom of the Container. | | 2 | ColliderTop: ContainerInner is fixed at the top of the viewport area, including SpaceTop. | | 3 | ColliderBottom: ContainerInner is fixed at the bottom of the viewport area, including SpaceBottom. | | 4 | TranslateY: ContainerInner is offset along the Y-axis relative to the Container. | | 5 | Rest: Rendering is paused until the state changes. |


Direction

Defines the direction of the viewport.

| Value | Description | | ----- | -------------------------------------- | | 0 | None: No movement in the viewport. | | 1 | Down: The viewport is moving downward. | | 2 | Up: The viewport is moving upward. |


Strategy

Defines the sticky behavior strategy.

| Value | Description | | ----- | ------------------------------------------------------------- | | 0 | None: No sticky behavior is applied. | | 1 | Both: Sticky behavior is applied on both edges of the Y-axis. | | 2 | Top: Sticky behavior is applied only at the top edge. |


Callback

type Callback = (state: State, direction: Direction, strategy: Strategy) => void;

See also: State, Direction, Strategy


Development

Follow the conventions/practices/rules described in this repository.

Building

npm run build
# expected: dist directory with build files

Links


Copyright and License

Copyright © 2026 Mikhail Prugov. Code released under the MIT License.