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

@rarexlabs/gensheets

v0.3.0

Published

Printable / presentable resource-page primitives for React (Sheet, ResourceTopBar, PresentMode, Section, CheckBullet, Shot) plus a scaffolding + PDF export CLI.

Downloads

43

Readme

@rarexlabs/gensheets

Printable, presentable, PDF-exportable resource pages for React — build one-off docs and decks (pitch decks, one-pagers, workflow sheets, spec sheets, whatever) as ordinary React components, styled with your app's own Tailwind tokens, with print/PDF/present-mode handled for you.

Framework-agnostic: works in any React 18+ app (Next.js App Router, Vite, Remix, plain SPA). Zero dependency on Next.js.

Key features

  • Two page shapes — doc (portrait, US Letter) and deck (landscape slides), both built from the same Sheet primitive
  • Print & PDF out of the box — a print stylesheet keyed off the Sheet markup, plus a Playwright-based generator/CLI that renders any URL to PDF
  • Fullscreen present mode — PresentMode turns a deck into a keyboard-navigable, laser-pointer-enabled slideshow, no extra markup needed
  • A small shared component set — Section, CheckBullet, Shot cover the recurring content patterns (titled sections, feature lists, screenshots) so pages don't reinvent them
  • Scaffolding CLI — npx gensheets create doc|deck generates a new page from a template instead of hand-copying an existing one
  • Own a component or import it — Section, CheckBullet, Shot can be ejected into your own components directory for full editing control (npx gensheets add), instead of always imported from the package
  • Sensible default styling, fully overridable — namespaced --gensheets-* tokens ship with real default values, so a consumer who does nothing gets a sane look, and reskinning is just redefining a CSS variable
  • Ships an AGENTS.md — so an agent working on your pages knows which components exist and how a new page should be structured, without having to infer it from reading existing files

Install

npm i @rarexlabs/gensheets
npm i react react-dom lucide-react   # required peers

PDF generation needs two additional peers, only if you use it:

npm i -D playwright pdf-lib

Copy or link this package's AGENTS.md into your own app's AGENTS.md/ CLAUDE.md so an agent editing your pages inherits the component/token conventions.

CSS custom-property contract

Components are styled entirely via Tailwind utility classes against namespaced --gensheets-* tokens, shipped with real default values in @rarexlabs/gensheets/tokens.css:

| Token | Default (oklch) | Used for | | ----------------------- | --------------------------------------- | ------------------------------------------ | | --gensheets-primary | oklch(0.205 0 0) — near-black | primary text color | | --gensheets-secondary | oklch(0.371 0 0) — dark gray | secondary/body text color | | --gensheets-muted | oklch(0.552 0 0) — mid gray | muted text, idle icon color | | --gensheets-border | oklch(0.869 0 0) — light gray | borders/dividers | | --gensheets-elevated | oklch(0.967 0 0) — near-white gray | elevated surface background | | --gensheets-accent | oklch(0.546 0.215 262.9) — blue | accent/interactive color | | --gensheets-paper | oklch(1 0 0) — white | the printable page's surface | | --gensheets-canvas | oklch(0.967 0.001 286.4) — light gray | on-screen preview background behind sheets | | --gensheets-success | oklch(0.527 0.154 150.1) — green | checkmark/success indicator |

A consuming app that imports tokens.css and defines nothing else gets this neutral default look immediately. To reskin, redefine any --gensheets-* variable in your own CSS — no component code is ever touched:

:root {
  --gensheets-accent: oklch(
    0.6 0.23 286
  ); /* swap the default blue for your brand purple */
}

Multiple themes (multi-tenant / per-client styling)

An app that generates docs/decks for more than one brand or client (e.g. a consultancy producing deliverables per client engagement) doesn't need a different install per theme — scope the same override to an attribute selector instead of :root, and tag each client's route/section root with the matching attribute:

/* your app's globals.css / app.css */
[data-gensheets-theme="acme"] {
  --gensheets-accent: oklch(0.6 0.23 286);
}
[data-gensheets-theme="globex"] {
  --gensheets-accent: oklch(0.55 0.18 25);
}
// e.g. the layout wrapping that client's printable routes
<div data-gensheets-theme="acme">{children}</div>

--gensheets-* custom properties inherit down the DOM like any other CSS variable, so every Sheet rendered under that wrapper picks up its client's overrides while everything outside it keeps the shared default (or another client's overrides) — no gensheets code or config is involved, and nothing here is specific to this package. Attribute name and value are entirely up to you; data-gensheets-theme="<client>" is just a suggested convention.

Import tokens.css from inside your own Tailwind entry stylesheet — the file that already has @import "tailwindcss"; — not via a bare JS-level import "@rarexlabs/gensheets/tokens.css" in a layout/component:

/* your app's globals.css / app.css */
@import "tailwindcss";
@import "@rarexlabs/gensheets/tokens.css";

This matters: Tailwind v4's @theme inline merging (which is what turns --gensheets-primary into a real text-gensheets-primary utility class) only applies within the CSS import graph reachable from a Tailwind root. A stylesheet pulled in purely via a JS-level import can end up bundled as an isolated asset outside that graph — the variable would still exist, but the utility classes components rely on (text-gensheets-primary, etc.) would never get generated, and everything would silently render unstyled. print.css, by contrast, is plain CSS with no @theme coupling and can be imported either way.

Also add an explicit @source for this package under Tailwind v4. Tailwind v4's automatic content detection skips node_modules by default, but Sheet/ResourceTopBar/Section/etc. ship their class names as plain strings inside this package's compiled dist/index.js — without an explicit source, Tailwind never sees them and none of the utilities they use (bg-gensheets-paper, shadow-xl, ...) get generated, so components render with the right class names but no actual styling:

/* your app's globals.css / app.css */
@import "tailwindcss";
@import "@rarexlabs/gensheets/tokens.css";
@source "../node_modules/@rarexlabs/gensheets/dist";

Adjust the relative path to match where your Tailwind entry stylesheet lives relative to node_modules.

Docs vs. decks

  • doc — one or more US Letter pages. <Sheet> with no variant, portrait (8.5in × 11in) by default; pass orientation="landscape" (11in × 8.5in) for a doc that needs the extra horizontal room (e.g. a wide table). Convention: docs/<slug>/page.tsx.
  • deck — one or more 16:9 landscape slides. <Sheet variant="slide">. Always landscape — orientation is ignored. Convention: decks/<slug>/page.tsx.

Both are just stacks of <Sheet> — a doc that needs pagination and a deck with several slides both render multiple Sheets in a row.

Components

import {
  Sheet,
  ResourceTopBar,
  Section,
  CheckBullet,
  Shot,
} from "@rarexlabs/gensheets";
import "@rarexlabs/gensheets/print.css";

export default function Pricing() {
  return (
    <>
      <ResourceTopBar />
      <Sheet className="flex flex-col font-sans">
        <Section title="Overview">
          <CheckBullet>Ships with sensible defaults</CheckBullet>
          <Shot label="Product screenshot" src="/pricing/screenshot.png" />
        </Section>
      </Sheet>
    </>
  );
}
  • Sheet — a single printable page. Defaults to US Letter portrait (8.5in × 11in); orientation="landscape" flips a doc to 11in × 8.5in; variant="slide" switches to 16:9 landscape (10in × 5.625in) regardless of orientation. Renders a data-sheet attribute that print.css and the PDF generator both key off, and manages its own @page size at print time (see "Landscape & the @page override" below). Package-only — never ejectable, it carries the print/PDF contract.
  • ResourceTopBar — back link, share, print, and PDF-download button (shown once a HEAD request to the matching .pdf path succeeds). Renders <PresentMode /> internally — you don't import PresentMode directly. Package-only.
  • PresentMode — fullscreen slide presenter for <Sheet variant="slide"> decks. Renders nothing on pages with no slide sheets. Package-only.
  • Section — a titled content block with a heading + hairline divider. Ejectable — see below.
  • CheckBullet — a single checkmark bullet, for feature/benefit lists. Ejectable.
  • Shot — a bordered, rounded frame around a screenshot/image (plain <img>, not next/image). Ejectable.

Import print.css once, wherever your printable-page layout lives.

Import it, or own it

Section, CheckBullet, and Shot can be used either way:

// Default: import from the package
import { Section } from "@rarexlabs/gensheets";
# Or eject it and own the source
npx gensheets add section
// Then import your own copy
import { Section } from "@/components/gensheets/Section";

Reach for the package import by default. Eject when a specific document needs a reskinned or extended version of the component that isn't worth threading through props. Sheet/ResourceTopBar/PresentMode have no eject option — they encode the print/PDF/present pipeline and forking them per consumer would risk silently breaking it. See "Ejecting a component to local ownership" below for the full mechanics.

Adding a new shared component

If a content pattern repeats across more than one page (another kind of callout, a table style, whatever), add it to this package rather than duplicating it per-page — export it from src/index.ts and document it here and in AGENTS.md so both humans and agents know it exists.

Landscape & the @page override

print.css sets a US Letter portrait @page as a static fallback, but Sheet overrides it itself at print time to match its own variant/ orientation — mounting (and on every beforeprint event) it injects a <style> tag with the matching @page { size: ...; margin: 0 } and --sheet-width/--sheet-height, then moves that tag to the very end of <head> right before printing (appendChild on an already-inserted node moves it, so it wins the cascade regardless of when other stylesheets were injected). This is why orientation="landscape" (or variant="slide") on a Sheet is enough on its own — no per-page script needed.

The PDF generator dispatches a beforeprint event before calling page.pdf(), so this same override applies during PDF export too. A page with more than one Sheet should keep them all the same variant/orientation — the PDF generator sizes the whole export off the first [data-sheet] element.

Scaffolding new pages

npx gensheets create doc --slug pricing --title "Pricing"
npx gensheets create deck --slug acme-pitch --title "Acme Pitch"

Writes page.tsx from a bundled template into src/app/resources/(printable)/<docs|decks>/<slug>/page.tsx by default (override the base with --out-dir <path>, or set resourcesDir in gensheets.config.json — see below). --title defaults to a title-cased version of --slug if omitted. Fails rather than overwriting if the target file already exists.

The generated page's imports automatically follow whatever you've ejected: if you've run gensheets add section, a newly scaffolded doc imports Section from your local components directory instead of the package — nothing needs to be configured explicitly for this, see the next section.

Ejecting a component to local ownership

gensheets.config.json (optional — all fields fall back to the defaults shown, create it with npx gensheets init) controls where ejected components live and where the scaffolding CLI writes new pages:

{
  "componentsDir": "src/components/gensheets",
  "componentsAlias": "@/components/gensheets",
  "resourcesDir": "src/app/resources/(printable)"
}
npx gensheets init             # write gensheets.config.json with defaults
npx gensheets add section      # copy Section.tsx into componentsDir
npx gensheets add check-bullet
npx gensheets add shot

Fails rather than overwriting an existing file at the target path unless you pass --force (useful for pulling in the package's latest version of a component you'd previously ejected and haven't since edited).

Once ejected, gensheets create doc picks it up automatically — it checks whether the file exists at componentsDir at generation time, no separate flag or mode to set. For example, after npx gensheets add section only (leaving CheckBullet un-ejected), a freshly generated doc page's imports look like:

import { Sheet } from "@rarexlabs/gensheets";
import { CheckBullet } from "@rarexlabs/gensheets";
import { Section } from "@/components/gensheets/Section";

With nothing ejected, the same generation produces a single grouped import line instead:

import { Sheet } from "@rarexlabs/gensheets";
import { Section, CheckBullet } from "@rarexlabs/gensheets";

Pages already generated before you eject a component are not retroactively rewritten — this only changes what newly scaffolded pages do.

PDF generation

import { generatePdfs } from "@rarexlabs/gensheets/pdf";

await generatePdfs({
  baseUrl: "http://localhost:4173",
  pages: ["/resources/decks/acme-pitch/", "/resources/docs/pricing/"],
  outDir: "./public/resources",
});

Or via the CLI, against any URL that serves your rendered pages (a dev server, a preview server, or a deployed site):

npx gensheets pdf \
  --base-url http://localhost:4173 \
  --pages /resources/decks/acme-pitch/,/resources/docs/pricing/ \
  --out-dir ./public/resources

# or, for a larger list:
npx gensheets pdf --base-url http://localhost:4173 --pages-file pages.json --out-dir ./public/resources

Each target page must render at least one [data-sheet] element. The first sheet's rendered size determines the PDF page dimensions.

Versioning

Standard semver. Patch = fixes, minor = additive API, major = breaking changes. dist/ is committed to this repo (not just published output) so that a git-installed tag is a complete, buildable-once artifact — package managers have historically inconsistent prepare-script behavior for git dependencies.