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

@manishrc/keyline

v0.3.1

Published

Figma-style alignment guides for the browser. Drop a data-keyline attribute on any element — see guides for it on a dev overlay.

Readme

keyline

Figma-style alignment guides for your project — directly in the browser. Committed to the repo, so the whole team designs to the same system.

You're the obsessive designer who felt pain looking at things that didn't align. In Figma or Sketch you set up your keylines, your column grid, your baseline rhythm — and you shipped pixel-perfect.

Now everyone's coding with AI. It's easier than ever to design directly in code. But you lost the helpers your design tool gave you. There's no grid panel in the browser. No baseline rhythm. No margin guides. You eyeball it. The drift creeps in.

keyline brings those helpers back, right over your running app. And because the configuration lives in the repo, every teammate (and every coding agent) follows the same alignment system — automatically.

Drop one component at root, mark your content container with one attribute, press K.

// app/layout.tsx
<Keyline margins={{ width: 24, anchor: "content" }} />

// Mark your content container, anywhere:
<div className="mx-auto max-w-2xl" data-keyline="content">
  <Article />
</div>

That's it. Press K.


Install

bun add @manishrc/keyline    # or npm / pnpm / yarn

What you get on the keyboard

| Key | Action | |---|---| | K | Show / hide overlay (the global key) | | M | Margin strips | | C | Column grid | | B | Baseline rhythm | | L | Positional lines (declared in config) | | R | Rulers (drag from a ruler to drop a local guide) | | B + click | Scope the baseline grid to one component (rest of page dims) | | B + drag | Nudge the grid phase while scoped | | Alt + click | Select a measurement reference (then hover to measure between) | | P | Pin the current measurement | | ? | Show the keyboard panel | | Esc | Clear selection / hide the overlay |

The family keys (M / C / B / L / R / ? / Esc) only listen while the overlay is showing. That keeps the host app's keyboard untouched until you press K — then for the duration of overlay-mode, the keyboard belongs to keyline.

You don't need to memorize the keys: the first few times you press K, a small HUD shows them. After that it goes quiet. Recall it any time with ?.

Ignored while you're typing in inputs. Last state persists across reloads. The floating button in the corner is the mouse equivalent — hover for per-family toggles, click to show/hide.


The mental model

Everything lives on one shared overlay above your page. Five families:

  • Margins — tinted strips marking the "safe area" inside your content container (Apple HIG / Material style).
  • Columns — the column grid, automatically sitting inside the margins (Figma frame model).
  • Baseline — horizontal rhythm grid for vertical alignment.
  • Positional lines — single guides at fixed coordinates (optional, declared in config).
  • Rulers + local guides — drag from the rulers to drop scratch guides for pixel-perfect spot checking. Stored in your browser, scoped to the page.

You only need to know one mechanism: drop <Keyline /> once with your config, mark your content container with data-keyline="content". Margins anchor to that element. Columns auto-anchor to the same place, inset by the margin width. Refactor max-w-2xl to max-w-3xl and everything moves with the className. No second source of truth.


A complete setup

For most apps, this is the whole config:

// app/layout.tsx
import { Keyline } from "@manishrc/keyline";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        {process.env.NODE_ENV === "development" && (
          <Keyline
            margins={{ width: 24, anchor: "content" }}
            columns={{ count: 12, gutter: 16 }}
            baseline={{ step: 4, emphasizeEvery: 4 }}
          />
        )}
      </body>
    </html>
  );
}

Then on any page's content container:

<div className="max-w-2xl mx-auto px-6 py-8" data-keyline="content">
  <PageBody />
</div>

Press K. You see margins on each side, a 12-column grid centered between them, a 4px baseline rhythm with major lines every 16px, and a thin ruler along the top and left edges.

Change max-w-2xl to max-w-3xl — the margins and columns follow. The class is the layout AND the guide.

Responsive buckets

Any family accepts per-viewport values, keyed by phone / tablet / desktop (mobile-first fallback, like Tailwind prefixes). Buckets are decided by the viewport's shortest side, so rotating a phone never flips the bucket:

<Keyline
  columns={{
    phone: { count: 4, gutter: 12 },
    tablet: { count: 8, gutter: 16 },
    desktop: { count: 12, gutter: 16, maxWidth: 1280 },
  }}
/>

Rulers & local guides

When the overlay is on, two thin rulers appear along the top and left edges. Drag from a ruler onto the page to drop a guide. The guide snaps to the nearest column edge, baseline line, margin edge, or declared line within 6 px. Hold Shift while dragging to disable snapping.

  • Drag an existing guide to reposition it (or off the page, back into the ruler, to delete it).
  • Alt-drag a guide to duplicate it (Figma-style — the copy stays put).
  • Double-click a guide to delete it.
  • Click the corner square (top-left, where the rulers meet) to clear every guide on this page.
  • R toggles the rulers without removing existing guides.

Local guides are scoped to the current path (location.pathname — query strings and hashes are ignored) and persist in localStorage under keyline:local-guides:v1:<path>. They're per-developer — they don't follow you across machines, and they don't end up in the repo. That's intentional: declared lines in your <Keyline> config are the team contract; local guides are your scratch pad.

Visually, declared lines are solid; local guides are dashed so the eye separates "this is the system" from "this is mine."

To turn the whole feature off:

<Keyline rulers={false} />

To loosen or tighten the snap:

<Keyline rulers={{ snapDistance: 12 }} />

Zero-impact promise

Adding or removing keyline causes zero changes to your app's CSS or layout. Delete the package — your rendered output is pixel-identical.

How:

  • No wrappers around your elements.
  • No marker children that would shift :nth-child or :empty.
  • No refs to manage.
  • No imports in production builds.

The only DOM footprint:

  1. A data-keyline="..." attribute on YOUR existing element (you add it; you remove it).
  2. One overlay <div> and one floating button appended to <body> at dev-mode runtime. Removed when the activator unmounts.

Don't style on [data-keyline] in your app's CSS — it's a tooling marker, not a styling hook.


What you should know

  • Production safety. Tree-shaken to zero bytes when NODE_ENV === "production".
  • Responsive automatically. Whatever your Tailwind resolves to is what gets measured — no breakpoint config in keyline.
  • Route changes. Margins and columns re-anchor automatically when each page mounts its data-keyline="content" element. Add the attribute on every primary route.
  • Multiple containers. Several elements with the same data-keyline value are merged at the union of their bounding rects.
  • Forgot the activator? If keyline sees [data-keyline] attributes but no <Keyline /> mounted, it logs a one-time dev hint.

That's it.

Most apps don't need anything below this line.


If you need more

Don't have a data-keyline element on a page yet

If your activator declares an anchor (margins={{ anchor: "content" }}) but a page is missing the attribute, margins fall back to the viewport edges. Just add data-keyline="content" to that page's container.

Hide the floating button

<Keyline button={false} />

// or move it:
<Keyline button={{ corner: "bottom-left" }} />

You can also just drag the button to any corner — the position persists per-developer and overrides the prop.

Margins without an anchor (viewport edges)

If you'd rather margins always sit at the viewport edges, omit anchor:

<Keyline margins={{ width: 24 }} />

Columns without margins (standalone)

If you don't use margins but want a centered column grid:

<Keyline columns={{ count: 12, gutter: 16, margin: 24, maxWidth: 1280 }} />

The grid centers itself in the viewport with the given margin inset and maxWidth cap.

Positional guides (single lines at fixed coordinates)

For ad-hoc rules not tied to a container:

<Keyline
  lines={[
    { left: "50%", label: "midpoint" },
    { top: "4rem", label: "header-end" },
    { bottom: "env(safe-area-inset-bottom)" },
  ]}
/>

Numbers are pixels. Strings are any CSS length the browser understands — "24px", "1.5rem", "50%", "calc(50% - 16px)", "min(100%, 1024px)", "10vw".

Toggle with L or the floating panel.

Different colors

<Keyline color="oklch(70% 0.18 230)" />

Sets the keyline / margin hue. Columns keep their own blue by default — override via the theme prop:

<Keyline
  color="oklch(70% 0.18 230)"
  theme={{ column: "rgba(255, 200, 0, 0.16)" }}
/>

Per-element color override:

<div data-keyline="content" data-keyline-color="rgba(255,60,60,0.7)">

Auto-invert against any background

<Keyline blend />

Uses mix-blend-mode: difference. Works on most backgrounds; can fade on mid-gray gradients.

K is taken in my app

<Keyline hotkeys={{ toggleAll: "shift+k" }} />

Combos accept shift, alt, ctrl, cmd. Empty string disables. Family keys (M / C / B / L / R / ?) are likewise overridable — but remember they only fire when the overlay is on, so collisions are usually fine.

Presets

<Keyline preset="12-col" />
// presets: "8pt", "4pt", "12-col", "bootstrap"

My design system has a <Container> component

The DS author adds the attribute once:

function Container({ variant, children }) {
  const classes = cva(/* … */)({ variant });
  return (
    <div className={classes} data-keyline={variant}>
      {children}
    </div>
  );
}

Consumers don't import keyline. Every <Container> auto-contributes a guide when the app has the activator mounted. If keyline isn't installed, the attribute is inert.

Not using React

import { mount } from "@manishrc/keyline/core";
mount({ margins: { width: 24, anchor: "content" } });

Vue / Svelte / Astro: call mount() in the framework's mount hook. The data-keyline attribute works identically.


Reference

<Keyline /> props

| Prop | Type | Default | Description | |---|---|---|---| | margins | { width: Offset; anchor?: string } \| ByBucket \| false | — | Tinted strips inside the anchored element. No anchor = viewport edges. | | columns | { count, gutter, margin?, maxWidth?, fill? } \| ByBucket \| false | — | Column grid. Auto-anchors to margins.anchor when set. | | baseline | { step: number; emphasizeEvery?: number } \| ByBucket \| false | — | Baseline rhythm grid. | | lines | PositionalLine[] | [] | Single guides at fixed coordinates. | | rulers | boolean \| { snapDistance } | true | Top + left rulers, drag to drop local guides. | | color | string | rgba(236,72,153,0.7) | Project color for keylines and margins. | | theme | Partial<KeylineTheme> | derived | Per-family color overrides. | | hotkeys | Partial<KeylineHotkeys> | { toggleAll: "k", ... } | Keyboard shortcuts. | | preset | "8pt" \| "4pt" \| "12-col" \| "bootstrap" | — | Baseline + columns shortcut. | | blend | boolean | false | mix-blend-mode: difference overlay. | | button | boolean \| { corner?: ... } | { corner: "bottom-right" } | Floating control button. | | zIndex | number | MAX_SAFE_INTEGER | Overlay stacking. | | startHidden | boolean | true | Start with overlay off. |

data-keyline attributes

| Attribute | Type | Description | |---|---|---| | data-keyline | string | Label / anchor name. Multiple elements with the same value are merged. | | data-keyline-margin | CSS length | Per-element margin width override. | | data-keyline-color | CSS color | Per-element color override. |

Offset values (used by lines, margins.width, etc.)

| Value | Meaning | |---|---| | 24 or "24px" | 24 pixels | | "1.5rem" | Browser-resolved rem | | "50%" | Percent of the anchor's width/height | | "calc(50% - 16px)" | Full CSS math | | "env(safe-area-inset-bottom)" | Environment variables | | Any CSS length | Browser-parsed via hidden probe |


Common gotchas

  • content-visibility: auto on a [data-keyline] element collapses the rect to the size hint when offscreen. Set contain-intrinsic-size to a realistic value.
  • Shadow DOM: closed shadow roots aren't discoverable via document.querySelectorAll. Annotate the host element instead.
  • You see nothing after pressing K: clear localStorage (keyline:state:v1), or check that your container has non-zero measurable width.
  • Margins look huge on a narrow viewport: margins suppress when they'd exceed ~40% of the container width. Below that threshold they hide automatically.
  • Crisp lines on retina: rendered as 1 device pixel (0.5 CSS px on 2x), matching native iOS dividers.

What it doesn't do

  • Doesn't enforce — surfaces.
  • Doesn't override your CSS — overlays on <body>.
  • Doesn't add anything inside your app's DOM tree — only the attribute YOU wrote, plus one overlay node on <body>.
  • Doesn't ship in production — tree-shaken.

For coding agents

Keyline treats agents as first-class users — both at setup and at runtime.

Setup recipe (deterministic — do these in order)

This is the exact sequence to add keyline to a React / Next.js App Router app. Each step is verifiable; don't skip the checks.

  1. Install (use the project's package manager — detect it from the lockfile):

    bun add @manishrc/keyline        # bun.lockb
    npm i @manishrc/keyline          # package-lock.json
    pnpm add @manishrc/keyline       # pnpm-lock.yaml
  2. Mount the activator once, at the app root, dev-gated. In Next.js App Router this is app/layout.tsx; in Vite/CRA it's the root component:

    import { Keyline } from "@manishrc/keyline";
    // ...inside <body>, AFTER {children}:
    {process.env.NODE_ENV === "development" && (
      <Keyline margins={{ width: 24, anchor: "content" }} />
    )}

    <Keyline /> already ships "use client", renders null, and mounts its own overlay on <body>. You do not need to add "use client" to the layout — importing a client component into a server component is allowed.

  3. Mark the content container on each primary route — the element whose width defines the layout (often the one with max-w-* / mx-auto):

    <div className="mx-auto max-w-2xl" data-keyline="content">
  4. Next.js only — pre-empt the "two Reacts" error. Add this even before you see it; it's the #1 setup failure and harmless if unneeded:

    // next.config.ts
    const nextConfig = { transpilePackages: ["@manishrc/keyline"] };
    export default nextConfig;
  5. Verify without a browser: start the dev server and confirm the build compiles, then read the geometry programmatically (see runtime section) — window.keyline.snapshot() returns the resolved geometry once <Keyline /> is mounted and a [data-keyline] element is on the page. (It throws a descriptive error if the activator is missing or called server-side — treat that as a setup signal, not a crash.) If you can drive a browser, press K and confirm an overlay appears.

Failure modes — detect and self-correct

| Symptom | Cause | Fix | |---|---|---| | Invalid hook call / "two Reacts" | bun link/monorepo bundles a 2nd React | Add transpilePackages: ["@manishrc/keyline"] to next.config.ts (step 4). For Vite, dedupe React in resolve.dedupe. | | Cannot read … of undefined at mount() server-side | Called mount() during SSR / in a server component | Use <Keyline /> (it's "use client"), or guard mount() with if (typeof window !== "undefined"). | | Press K, nothing appears | Stale persisted state, or container has 0 measurable width | Clear localStorage key keyline:state:v1; confirm the [data-keyline] element has non-zero rendered width. | | One-time console hint: activator missing | [data-keyline] exists but no <Keyline /> mounted | Mount <Keyline /> once at root (step 2). | | Margins jump to viewport edges on a route | That route's container lacks data-keyline="content" | Add the attribute to that page's container (step 3) — it's per-route. | | Overlay shows but a marked element is missing | content-visibility: auto collapsed its rect, or it's in a closed shadow root | Set contain-intrinsic-size; for shadow DOM annotate the host element. | | data-keyline styles leak into the app | Used [data-keyline] as a CSS selector | It's a tooling marker, never a styling hook — remove the selector. | | Overlay present in the production bundle | Mount wasn't dev-gated | Wrap in process.env.NODE_ENV === "development" (step 2). It tree-shakes to zero only when gated. |

Runtime: reading alignment without screenshots

  • window.keyline.snapshot() (also exported as snapshot()) returns the resolved geometry — bucket, column edges, baseline lines, margin edges, containers, and per-container drift. An agent can check alignment without screenshots. It throws a descriptive error (not a silent failure) if called server-side or before <Keyline /> is mounted — the message tells you which.
  • Drift transitions are logged: [keyline] content: x +3 off column (was aligned) — console-reading agents see misalignment appear and disappear as they edit.
  • The data-keyline attributes are the shared vocabulary: the same labels humans see in the overlay are the keys agents see in the snapshot.

Coming later

  • Touch support (long-press to measure, bottom-sheet controls).
  • keyline.audit() returning structured violations for CI.
  • Export a local guide → declared lines config entry (so scratch becomes spec).

MIT.