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

@tleblancureta/prez

v0.4.2

Published

Framework-agnostic React presentation system. Author slide decks as TSX, render with a built-in viewer (keyboard nav, fullscreen, grid, PDF export). Pairs with the `prez` Claude skill for guided integration into any React app.

Downloads

1,270

Readme

@tleblancureta/prez

Framework-agnostic React presentation system. Author slide decks as TSX, render with a built-in viewer (keyboard nav, fullscreen, grid view, PDF export).

  • 🎯 Pure React + Tailwind. No Next.js, no router coupling. Wire it to App Router, Pages Router, Vite, Remix, react-router — anything.
  • 🧱 17 slide layouts out of the box (cover, agenda, story, bullets, comparison, table, timeline, calendar, checklist, takeaway, …) plus building blocks (Callout, IconCard, NumberCard).
  • ⌨️ Keyboard nav (arrows / space / f / g / ? / l), fullscreen, grid thumbnail view, deep-linkable ?slide=N.
  • 🖨️ PDF export via html2canvas-pro + jspdf (both optional peer deps; only loaded when the user clicks download).
  • 🔒 Optional password gate with client-side or server-verified modes.
  • 🤖 Pairs with the prez Claude skill so an AI agent can scaffold the integration into any host app.

Status: 0.2.0 — usable but iterating. Public API is stable; skill recipes evolve.


Install

npm install @tleblancureta/prez lucide-react
# Optional, only needed if you use PDF export:
npm install html2canvas-pro jspdf

Required peer deps: react ≥ 18, react-dom ≥ 18, lucide-react, tailwindcss ≥ 3.4.


Quickstart (60 seconds)

1. Create a registry and your first deck

// src/lib/prez.ts
import { createRegistry, type Presentation } from "@tleblancureta/prez";
import { CoverSlide, BulletSlide, TakeawaySlide } from "@tleblancureta/prez";

export const prez = createRegistry();

prez.register({
  slug: "intro",
  title: "Hola mundo",
  description: "Mi primera deck con @tleblancureta/prez",
  createdAt: "2026-04-28",
  slides: [
    {
      id: "cover",
      content: <CoverSlide title="Hola mundo" subtitle="Una deck de prueba" />,
    },
    {
      id: "bullets",
      content: (
        <BulletSlide
          title="Tres puntos"
          items={[
            { text: "Prez es framework-agnóstico" },
            { text: "Cada slide es un componente React" },
            { text: "Exporta a PDF con un click" },
          ]}
        />
      ),
    },
    {
      id: "cta",
      content: (
        <TakeawaySlide
          title="Listo!"
          items={["Crear", "Compartir", "Iterar"]}
          cta={{ label: "Empezar", url: "https://fizko.ai" }}
        />
      ),
    },
  ],
});

2. Render the viewer

// In any client-rendered page / route
import { SlideViewer } from "@tleblancureta/prez";
import { prez } from "@/lib/prez";

export function PrezPage({ slug }: { slug: string }) {
  const presentation = prez.get(slug);
  if (!presentation) return <div>Not found</div>;

  return <SlideViewer slides={presentation.slides} title={presentation.title} />;
}

That's it. URL deep-linking (?slide=2), keyboard nav, grid view, fullscreen, and PDF export work out of the box.


API reference

createRegistry(): Registry

Creates an isolated presentation registry. Each registry is independent — useful for tests or multi-tenant apps.

const registry = createRegistry();
registry.register(presentation);     // throws on invalid shape
registry.get(slug);                   // → Presentation | undefined
registry.list({ includeUnlisted });   // → PresentationSummary[] sorted newest-first

<SlideViewer>

Main viewer. Pure React — no router imports. Manages URL state via window.history.replaceState by default.

<SlideViewer
  slides={presentation.slides}
  title={presentation.title}
  // Optional — for framework-controlled URL state:
  currentSlide={current}
  onSlideChange={setCurrent}
  // Optional — branded logo top-right of every slide:
  logo="/my-logo.png"
/>

When currentSlide and onSlideChange are both provided, the host owns URL state (use it with useSearchParams / useRouter / etc.). Otherwise, the viewer manages it internally.

<PasswordGate>

Two modes — client-side (password baked into bundle, fine for low-stakes gating) or server-verified (call your own endpoint).

{/* Client-side */}
<PasswordGate
  expectedPassword="mypassword"
  onUnlock={() => setUnlocked(true)}
/>

{/* Server-verified */}
<PasswordGate
  verify={async (input) => {
    const res = await fetch("/api/verify-prez", {
      method: "POST",
      body: JSON.stringify({ password: input, slug }),
    });
    return res.ok;
  }}
  onUnlock={() => setUnlocked(true)}
/>

useSlideUrlState(totalSlides, paramName?)

Optional helper for hosts that want ?slide=N URL sync without wiring their framework's router. Returns [currentSlide, setCurrentSlide].

const [current, setCurrent] = useSlideUrlState(slides.length);
return <SlideViewer slides={slides} title={title} currentSlide={current} onSlideChange={setCurrent} />;

Slide formats (16:9, 4:5, 1:1, 9:16, custom)

By default, slides render at 1920×1080 (16:9) — the traditional projector / webinar / pitch-deck format. To author Instagram posts, stories, square cards, or any custom canvas, set format on the Presentation:

prez.register({
  slug: "ig-launch-card",
  title: "Lanzamiento — feed",
  description: "Instagram feed post",
  format: "4:5",                    // 1080×1350 (Instagram feed)
  createdAt: "2026-04-28",
  slides: [/* ... */],
});

prez.register({
  slug: "ig-story",
  title: "Story de lanzamiento",
  format: "9:16",                   // 1080×1920 (story / reel / TikTok)
  // ...
});

prez.register({
  slug: "linkedin-square",
  title: "Card cuadrada",
  format: "1:1",                    // 1080×1080 (square)
  // ...
});

prez.register({
  slug: "billboard",
  title: "Cartel afuera",
  format: { width: 2400, height: 800 },  // custom — anything goes
  // ...
});

Behavior driven by format:

  • Viewer scales to fit the slide aspect on screen (portrait, square, ultrawide all work).
  • PDF export uses the exact dimensions; orientation is auto-detected (landscape if width ≥ height, else portrait).
  • Grid thumbnails keep a constant ~288px width; portrait formats get taller thumbnail cards.
  • Slide layout components (CoverSlide, BulletSlide, etc.) use h-full w-full so they adapt to any aspect — but for very different aspect ratios you may want to author custom JSX via ContentSlide for tighter control.

If you pass currentSlide + onSlideChange (controlled mode), also pass format to <SlideViewer> directly:

<SlideViewer slides={...} title={...} format="4:5" />

The package exports SLIDE_FORMATS (preset map), resolveDimensions(format), and the types SlideFormat, SlideFormatName, SlideDimensions for advanced use cases (e.g. computing scaled thumbnail sizes or building a format picker).


Mobile preview frame (Instagram chrome)

For decks meant to ship as social-media posts, set chrome: "instagram" to wrap the live viewer in an iPhone + Instagram-post mockup (status bar, account header with avatar/username, action bar, caption). Useful for previewing 4:5 / 1:1 / 9:16 decks the way they'll actually appear in-feed before exporting:

prez.register({
  slug: "ig-launch",
  format: "4:5",
  chrome: "instagram",
  chromeUsername: "miempresa",
  chromeCaption: "Una caption opcional que se muestra abajo del post.",
  // chromeAvatar: "/avatar.png", // optional, falls back to a gradient initial
  /* ... */
});

Notes:

  • The chrome only affects the live viewer. PDF export always captures the bare slide so you can publish the asset directly.
  • Works with any format, but is designed for mobile-first ratios (4:5, 1:1, 9:16).
  • The phone frame has fixed dimensions (430×880) and is itself scaled to fit the viewport — so the slide looks the same regardless of screen size.
  • Available chromes: "none" (default) and "instagram". More device frames can be added later (e.g. "twitter", "linkedin", custom phone shells).

You can also use <InstagramFrame> directly outside the viewer if you want to embed a single mocked-up slide somewhere else (a landing page, a portfolio, etc.):

import { InstagramFrame } from "@tleblancureta/prez";

<InstagramFrame
  slideContent={<MyHookSlide />}
  slideWidth={1080}
  slideHeight={1350}
  username="miempresa"
  caption="..."
/>

Slide layouts

All exported from @tleblancureta/prez:

| Layout | Use case | |---|---| | WaitSlide | Pre-event lobby | | CoverSlide | Title + subtitle + author + date | | SectionSlide | Section divider with big number | | AgendaSlide | Numbered TOC | | StorySlide | Narrative with stat + author + quote | | BulletSlide | Bullets + optional myth-buster | | ContentSlide | Generic title + custom JSX children | | TableSlide | Comparison table with colored headers | | TimelineSlide | Horizontal timeline with active step | | CalendarSlide | Multi-row calendar grid | | ErrorListSlide | Numbered errors + fixes | | ChecklistSlide | Multi-phase color-coded checklist | | ComparisonSlide | Before/after side-by-side | | TakeawaySlide | Key takeaways + CTA |

Building blocks (use inside ContentSlide):

  • Callout — green/yellow/red/blue/neutral note box
  • IconCard — icon + title + description card
  • NumberCard — big number + label

Integration recipes

Recommended: pair with the prez Claude skill. Run Claude in your project, ask it to "set up @tleblancureta/prez", and it scaffolds the right wiring for whatever stack you're on (Next App Router, Next Pages, Vite, Remix, etc.).

Manual integration recipes:

Next.js App Router

// app/prez/[slug]/page.tsx
import { notFound } from "next/navigation";
import { prez } from "@/lib/prez";
import { PrezClient } from "./PrezClient";

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const p = prez.get(slug);
  if (!p) notFound();
  return <PrezClient presentation={p} />;
}

// app/prez/[slug]/PrezClient.tsx
"use client";
import { useState } from "react";
import { SlideViewer, PasswordGate, type Presentation } from "@tleblancureta/prez";

export function PrezClient({ presentation }: { presentation: Presentation }) {
  const [unlocked, setUnlocked] = useState(!presentation.password);
  if (!unlocked) {
    return (
      <PasswordGate
        expectedPassword={presentation.password!}
        onUnlock={() => setUnlocked(true)}
      />
    );
  }
  return <SlideViewer slides={presentation.slides} title={presentation.title} />;
}

Vite + react-router

// src/routes/PrezRoute.tsx
import { useParams } from "react-router-dom";
import { SlideViewer } from "@tleblancureta/prez";
import { prez } from "@/lib/prez";

export function PrezRoute() {
  const { slug } = useParams();
  const p = slug ? prez.get(slug) : undefined;
  if (!p) return <div>Not found</div>;
  return <SlideViewer slides={p.slides} title={p.title} />;
}

PDF export

html2canvas-pro + jspdf are optional peer deps. The viewer renders fine without them — you'll just get a console error if the user clicks the download button without them installed.

npm install html2canvas-pro jspdf

Each slide is captured at 1920 × 1080 (the design canvas). The PDF is generated client-side and triggered as a direct download — no server round-trip needed.


Tailwind setup

Slides use Tailwind utility classes directly. Make sure your Tailwind content scan includes the package:

// tailwind.config.js (Tailwind 3)
export default {
  content: [
    "./src/**/*.{ts,tsx}",
    "./node_modules/@tleblancureta/prez/dist/**/*.{js,cjs}",
  ],
};

For Tailwind 4 (CSS-config), no extra setup is needed — the JIT scans imports automatically.


License

MIT © Akashi Labs