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

@pride7/frameseq

v0.35.0

Published

Build editable, AI-friendly presentations with a linear TypeScript DSL.

Readme

FrameSeq

Write slides as an editable TypeScript document—not a tree of presentation markup.

FrameSeq is an AI-friendly presentation framework with a linear authoring model, useful design defaults, chainable styling, and HTML/PDF/PPTX/Typst output. It feels like building an interface, but the source stays as direct and readable as a document.

CI Gallery

中文文档 · Try FrameSeq online · Explore the live Gallery · Read the documentation · See what changed

presentation({
  title: "A Better Way to Write Slides",
  author: "Ada Lovelace",
  theme: "minimal-academic",
});

slide().cover();

slide("Why FrameSeq");
text("Presentation code should describe ideas, not boilerplate.").lead();
bullets(
  "One linear TypeScript file",
  "Semantic layouts with useful defaults",
  "Tailwind styling without configuration",
  "Interactive HTML, PDF, editable PowerPoint, and Typst",
);

slide("Results").grid(3);
cell(0); metric("12 min", "To first draft");
cell(1); metric("1 file", "To maintain");
cell(2); metric("3 formats", "HTML · PDF · PPTX");

slide("The model");
text`Every command belongs to the current slide until the next $\operatorname{slide}()$ call.`;

No imports, wrapper components, nested DOM, or export statement are required in a .slides.ts entry file.

Table of contents

Live Gallery

The FrameSeq Gallery contains live, clickable presentations built from this repository: a product tour, a complete AI-oriented research example, and previews of all seven built-in themes. The previews are compiled FrameSeq pages rather than screenshots, so they always track the current runtime and themes.

Why FrameSeq

Easier to edit than raw HTML

HTML is an excellent rendering target, but presentation source written directly as HTML quickly mixes content, nesting, layout, and styling. FrameSeq keeps the author's intent visible:

slide("Architecture").split("40:60");

image("pipeline.png", "Compiler pipeline");

right();
text("Three clear stages").lead();
bullets("Parse the document", "Build the page tree", "Render HTML, PDF, or PPTX");

A slide starts with slide(); everything after it belongs to that slide until the next slide(). Layout changes are named operations such as split(), grid(), right(), and cell(). A local visual change stays attached to the object it affects.

This makes presentation code easier to read, rearrange, diff, and revise than a large hierarchy of generic elements.

A strong target language for AI

FrameSeq is deliberately small and regular, which makes it well suited to AI-generated presentations:

  • The source expresses presentation intent instead of DOM implementation details.
  • Slide boundaries are explicit and require no indentation tree.
  • Semantic commands such as lead(), bullets(), metric(), and split() reduce arbitrary design decisions.
  • Local edits usually change a few lines without rebuilding an HTML structure.
  • TypeScript catches misspelled APIs and invalid arguments.
  • frameseq check --json gives agents empty-slide, measured overflow, clipping, and readability diagnostics they can correct directly.
  • Themes and presentation-wide typography let AI change the visual system without rewriting every slide.
  • The FrameSeq runtime owns rendering, navigation, responsive scaling, and HTML, PDF, PPTX, and Typst output.

An AI can generate a useful first draft with the semantic API, then a person can edit the same compact source. When a page needs more control, both can progressively add Tailwind utilities, custom themes, or low-level objects without abandoning the document.

Give a coding agent the compact llms.txt contract, then use the AI generation guide for prompting, validation, and correction loops. The Gallery includes a complete AI-oriented research example and its editable source.

Progressive control

Start with defaults:

slide("One idea");
text("A normal slide already has spacing and typography.");
bullets("First point", "Second point");

Add a structured layout:

slide("Results").grid(3);
cell(0); metric("42%", "Growth");
cell(1); metric("18K", "Users");
cell(2); metric("99.9%", "Uptime");

Style one object:

text("Important")
  .style("text-4xl font-bold tracking-tight text-blue-600");

Or drop down to the explicit object API for a completely custom composition. FrameSeq does not force every slide into the same abstraction level.

Quick start

Open the editable FrameSeq Playground in StackBlitz to change slides.ts and see the live presentation without installing anything.

Create a presentation project:

npm create frameseq@latest my-talk
cd my-talk
npm install
npm run dev

The generated project contains:

my-talk/
├── AGENTS.md
├── CLAUDE.md
├── components/
│   ├── content.ts
│   └── theme.ts
├── slides.ts
├── package.json
└── tsconfig.json

Edit slides.ts; the browser preview updates as the file changes. Put reusable content functions in components/content.ts and project-wide design settings in components/theme.ts. The entry imports these files directly, without an extra index.ts layer. AGENTS.md gives coding agents the project's FrameSeq syntax, region, formula, and validation rules; CLAUDE.md imports the same instructions so they stay in sync.

To use FrameSeq in an existing TypeScript project:

npm install --save-dev @pride7/frameseq
npx frameseq new my-talk.slides.ts
npx frameseq dev my-talk.slides.ts

The document model

presentation("My Talk");

slide().cover();
text("Build slides like apps").hero();
text("Keep them as easy to edit as documents").subtitle();
text("Your name").author();

slide("Why");
text("Common slides use useful defaults.");
bullets(
  "No layout boilerplate",
  "KaTeX-powered formulas",
  "Browser preview and PDF export",
);

slide("Equation");
math`\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}`;

presentation() defines presentation-wide metadata and design options. slide() starts a page. Content commands add objects to the current page and return chainable builders.

The FrameSeq compiler injects the authoring commands and exports the presentation automatically. Ordinary TypeScript imports, variables, URLs, and functions remain available when the slides need them.

Content

Text roles

text("Main title").hero();
text("Supporting message").subtitle();
text("SECTION 01").eyebrow();
text("The important idea").lead();
text("Source: Example").caption();
text("A memorable sentence").quote();

Text roles provide consistent, theme-aware styling while remaining individually customizable.

Formulas

Use a tagged template for display math:

math`\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}`;

Use $...$ inside a tagged text template for inline math:

text`Euler's identity is $e^{i\pi} + 1 = 0$.`;

The tagged forms preserve backslashes, so String.raw is unnecessary.

Typst for complex typesetting

Keep FrameSeq in control of the presentation structure and use Typst for a complex local fragment. Install the optional build-time compiler:

npm install --save-dev @myriaddreamin/typst-ts-node-compiler

Then use a static tagged template:

slide("Optimization");
text("Objective function").lead();

typst`
  #set text(size: 22pt)
  $ min_theta sum_(i=1)^n
    loss(f_theta(x_i), y_i) + lambda norm(theta)^2 $
`
  .width(720);

For larger fragments, keep Typst in its own file:

typstFile("./figures/architecture.typ")
  .width(percent(100));

FrameSeq compiles embedded Typst to inline SVG during the Vite build. The browser receives no Typst compiler or WASM, and the fragment participates in normal FrameSeq layout and styling across HTML, PDF, and PPTX output. FrameSeq can also export the whole presentation as editable .typ source; native Typst fragments remain source, LaTeX formulas and basic text use MiTeX, and common LaTeX tables become native Typst tables. See Typst integration and export.

LaTeX tables

Keep KaTeX for ordinary formulas and use the optional Tectonic backend when a slide needs a real LaTeX table. Install it locally:

npm install --save-dev node-tectonic

Then place the table like any other FrameSeq object:

slide("Results");

latex`
  \begin{tabular}{lrr}
    \toprule
    Model & Accuracy & Latency \\
    \midrule
    Baseline & 91.2\% & 18 ms \\
    FrameSeq & \textbf{94.6\%} & 12 ms \\
    \bottomrule
  \end{tabular}
`
  .width(820)
  .position({ x: 180, y: 210 });

latexFile("./tables/results.tex") keeps larger fragments in their own files. FrameSeq compiles static fragments at build time, embeds the generated fonts, caches the result by content, and preserves it across HTML, PDF, and PPTX export. See LaTeX integration for supported packages and restrictions.

Code, lists, images, and reveals

code(`const answer = 42;`, "ts");

bullets("Readable source", "Useful defaults", "Flexible styling");

steps("Introduce the problem", "Reveal the model", "Show the result");

image("diagram.png", "System diagram");

steps() reveals items during navigation. PDF and PPTX export include the fully revealed slide.

Layout

FrameSeq uses a single-column layout by default. Add structure only where it communicates something useful.

Split

slide("Architecture").split("40:60");

image("diagram.png", "Compiler diagram");

right();
text("Compiler").lead();
bullets("TypeScript DSL", "HTML renderer", "PDF export");

Grid

slide("Comparison").grid(3);

cell(0);
text("Simple").lead();
text("Useful defaults");

cell(1);
text("Flexible").lead();
text("Tailwind or object styles");

cell(2);
text("Portable").lead();
text("HTML and PDF");

Region paths

Grouping usually forces source to nest. at(path) addresses a region instead, and creates the containers it names, so composition stays flat — one statement per object, no nesting, no closing call:

slide("Roadmap").grid(2);

at("cell0/now").card();
text("Q3").eyebrow();
bullets("Anchors", "Region paths");

at("cell1/next").card();
text("Q4").eyebrow();

at("cell0/now");
text("Merged into main").caption();

The first segment can address a region the layout already owns, so at("cell1") and cell(1) are the same region. Every other segment is created on first use, at any depth. Revisiting a path appends to the same region, which means a page can be written in the order that reads best. Paths reset with the next slide(), and each one doubles as an anchor name, so line().to("stages") can connect to a positioned region.

Local grid section

Keep the slide in its normal top-to-bottom flow and arrange only one group of objects as a grid. Each object passed to gridSection() becomes one cell:

slide("Results");

text("Performance this quarter");

gridSection(
  3,
  metric("42%", "Growth"),
  metric("18K", "Users"),
  metric("99.9%", "Uptime"),
).gap(20);

text("All targets were exceeded.");

Use card() for a common title-and-copy cell, or group() when one cell needs several independently styled objects:

gridSection(
  3,
  card("Declarative", "Readable source code"),
  card("Portable", "HTML, PDF and PPTX"),
  group(
    text("AI-friendly").bold(),
    text("Easy to generate and revise"),
  ).card(),
);

Unlike slide().grid(3), a local grid does not take over the whole slide body. Content before and after it remains in the ordinary document flow.

When the container reads better than its contents, name it with at() and keep writing normally:

at("panel").card().padding(24);
text("Revenue").bold();
text("$1.2M").size(42);

Calling .canvas() on a container creates a local coordinate system for positioned children, and .clip() keeps them inside its bounds.

Objects can also be grouped and restyled by name, which keeps the source free of local variables:

rect("Parse").as("parse");
rect("Build").as("build");
rect("Render").as("render");

group("parse", "build", "render").row().gap(16);
ref("parse").fill("#dbeafe");

ref(name) returns the same builder the object was created with, so shapes keep .fill(), connectors keep .arrow(), and containers keep .gap(). Names come from .as() or from an at() path.

Freeform positioning

Switch the slide body to a canvas when an object needs exact coordinates:

slide({ name: "System map" }).canvas();

text("Compiler")
  .position({ x: 80, y: 90 })
  .width(320)
  .size(32)
  .bold();

image("diagram.png", "Compiler diagram")
  .position({ x: 500, y: 80 })
  .width(620);

Coordinates are relative to the current canvas region. Numbers are pixels in FrameSeq's fixed presentation coordinate system; the default presentation canvas is 1280 x 720, and FrameSeq maps that finished geometry consistently to interactive HTML, PDF, and PPTX. Prefer structured layouts for most pages, then use canvas() with position() for diagrams and custom compositions.

Shapes and connectors

Create editable diagram nodes directly in the slide source. FrameSeq renders boxes and circles as HTML, while lines and arrowheads remain vector SVG:

slide({ name: "Pipeline" }).canvas();

line({ x1: 320, y1: 180, x2: 520, y2: 180 })
  .stroke("#2563eb")
  .strokeWidth(4)
  .arrow("end");

rect("TypeScript")
  .position({ x: 80, y: 125 })
  .width(240)
  .height(110)
  .fill("#dbeafe")
  .stroke("#2563eb")
  .radius(18);

circle("FrameSeq")
  .position({ x: 520, y: 100 })
  .width(160)
  .fill("#cffafe")
  .stroke("#0891b2");

Write connectors before nodes when the lines should appear behind them.

Diagrams that reference names instead of numbers survive editing. Name an object with as(), then place and connect the rest relative to it:

slide({ name: "Training loop" }).canvas();

rect("Encoder").as("enc").position({ x: 80, y: 140 }).width(200).height(100);
rect("Decoder").as("dec").rightOf("enc", 140);
circle("Loss").as("loss").rightOf("dec", 140);

line().from("enc").to("dec").arrow("end");
line().from("loss.bottom").to("enc.bottom").arrow("end");

text("shared vocabulary").caption().below("enc", 16);

A row or column removes the coordinates altogether: the container carries the only one, its children lay themselves out, and connectors still find them.

slide({ name: "Pipeline" }).canvas();

at("stages").row().gap(80).anchor("center");
rect("Parse").as("parse");
rect("Build").as("build");
rect("Render").as("render");

line().from("parse").to("build").arrow("end");
line().from("build").to("render").arrow("end");

Moving the diagram is one edit, and anchor() places the container against the slide, so this page has no coordinates at all. Placement modifiers are rightOf(), leftOf(), above(), below(), centerOn(), alignTop(), alignLeft(), and anchor(). Connectors pick the facing edges automatically, or take an explicit anchor such as "enc.top-right". Moving one box updates every connector attached to it. See Diagrams for the complete model — region paths, automatic layout, names, and connectors — and Shapes and connectors for the primitives, arrow directions, layering, and custom SVG assets.

Other layout tools include center(), fullBleed(), left(), and main().

Styling

Tailwind, built in

Pass Tailwind utilities directly to style(); no Tailwind config is required:

text("Designed in the source")
  .style("rounded-2xl bg-slate-900 px-8 py-5 text-3xl font-semibold text-white");

Arbitrary utilities such as text-[30px], left-[80px], and bg-[#0f172a] are supported.

Chainable modifiers

text("A precise annotation")
  .size(18)
  .weight(600)
  .color("#475569")
  .margin(12, 0);

The object overload of style() accepts inline CSS when needed:

text("Custom").style({
  letterSpacing: "0.08em",
  textTransform: "uppercase",
});

Themes and typography

FrameSeq starts with a neutral blank theme. Choose a bundled theme:

presentation({ title: "My Talk", theme: "midnight" });

Academic themes can build title bars, metadata footers, and slide numbers from presentation metadata:

presentation({
  title: "Diffusion Language Models",
  subtitle: "From iterative recovery to continuous state spaces",
  author: "Your Name",
  institute: "Your Institute",
  date: "2026",
  theme: "minimal-academic",
});

slide().cover();

Override presentation-wide typography without creating a theme:

presentation({
  title: "My Talk",
  theme: "minimal-academic",
  font: {
    family: '"Noto Sans SC", sans-serif',
    size: 24,
    heading: { family: '"Noto Serif SC", serif', weight: 700 },
    code: { family: '"JetBrains Mono", monospace', size: 18 },
  },
});

Or define a reusable design system:

const ocean = defineTheme({
  name: "ocean",
  colors: {
    background: "#effcff",
    foreground: "#15343b",
    accent: "#007c91",
  },
});

presentation({ title: "Ocean Research", theme: ocean });

Visual Studio Code extension

FrameSeq includes a companion VS Code extension that keeps slides.ts on the left and a live preview on the right. It also provides a slide outline with source-and-preview synchronization, current/previous/next slide navigation, a current-slide status item, slide insertion, TypeScript snippets, Problems-panel layout diagnostics, and HTML/PDF/PPTX/Typst export commands. It consumes the same local FrameSeq CLI as the terminal workflow, so there is only one rendering and validation implementation.

Synchronization also runs the other way. While frameseq dev is serving, hold Alt over the preview to outline the object under the pointer, and Alt-click it to jump to the command that wrote it: in the VS Code preview the cursor moves in the editor beside it, and in a browser the development server opens the file. A command inside a loop or a helper function produces several objects, so more than one of them can lead back to the same line. Source positions are recorded only while serving; a built presentation has none.

The E control goes one step further and turns on layout editing. An object placed with position({ x, y }) can then be dragged, and one given a width() or a height() can be resized from its corner; releasing the drag rewrites those numbers in slides.ts and the preview reloads from the new source. Only the digits change, so comments and formatting are untouched, and a drag is refused outright if the numbers no longer match the file on disk. Escape leaves the mode.

Ctrl+Z in the preview undoes the last drag. In a browser the development server keeps the text each drag replaced and puts it back verbatim; inside the VS Code preview the drag is applied through the workspace instead of written to disk behind the editor's back, so the editor's own undo covers it. Either way the history is dropped rather than misapplied once the document no longer matches it.

An object in the document flow has no coordinates to rewrite, so dragging it changes its place among its neighbours instead: the preview draws where it would land, and releasing it carries the whole lines its command occupies there, with any comment written above or beside it. This stays inside one run of the document—a left(), a cell(1), a new slide(), or a group(a, b) that collects the objects above it each end a run, since carrying lines across one would change which region an object belongs to rather than only its order.

Editing is deliberately limited to numbers the document states outright. position({ x: cursor, y: 90 }) computes its x, and for (const stage of stages) rect(stage) writes one command that becomes several objects; in both cases no single number stands for what was dragged, so FrameSeq offers no handle rather than guessing. The source stays the only description of the talk—dragging is just another way to type a number into it.

Build the current extension from this repository:

npm run vscode:package
code --install-extension output/vscode/frameseq-vscode.vsix

The extension lives in packages/vscode-frameseq and the complete setup guide is in Visual Studio Code extension.

Preview, build, and export

# Live browser preview
frameseq dev my-talk.slides.ts

# Static interactive website in dist/
frameseq build my-talk.slides.ts

# One self-contained dist/index.html
frameseq build my-talk.slides.ts --single-file

# PDF in output/pdf/
frameseq pdf my-talk.slides.ts

# Editable PowerPoint in output/pptx/
frameseq pptx my-talk.slides.ts

# Pixel-faithful PowerPoint with one image per slide
frameseq pptx my-talk.slides.ts --flatten

# Editable Typst source in output/typst/
frameseq typst my-talk.slides.ts

The default static build uses relative asset paths, so dist/ can be hosted at a domain root or a repository subpath such as GitHub Pages. New projects include an Actions workflow that publishes dist/ on pushes to main or master. The single-file build embeds the framework's scripts, styles, fonts, and favicon into one directly openable HTML file. PPTX export keeps normal text and shapes editable while using high-resolution image fallbacks for math, Typst, and LaTeX fragments; --flatten turns each complete slide into one image for maximum fidelity. Typst export keeps pages, grids, text, code, shapes, and positions editable, uses MiTeX for LaTeX math and basic text, and maps common LaTeX tables to native Typst tables. See Deploy HTML, Export PowerPoint, and Typst integration and export.

Arrow keys, Page Up/Page Down, and Space navigate the interactive presentation.

AI-friendly layout checks

FrameSeq can inspect the final browser geometry and return actionable diagnostics instead of asking an AI agent to guess from source code:

frameseq check my-talk.slides.ts
frameseq check my-talk.slides.ts --json
frameseq check my-talk.slides.ts --strict

The checker reports empty slides, objects outside the canvas, clipped text, and unreadably small type. Each issue includes the slide label, FrameSeq object type and path, relevant measurements, and suggested corrections. Mark a deliberate blank page with slide().allowEmpty(); --json produces stable machine-readable output for coding agents and CI. See AI-friendly layout checks.

Presenter view

Attach private notes to any slide:

slide("Architecture")
  .notes("Explain why FrameSeq owns structure while Typst owns complex local layout.");

While viewing the presentation, press P or use the P button to open presenter view in a second window. It shows the current slide, next slide, speaker notes, timer, page selector, navigation controls, and a synchronized virtual laser pointer. Press Ctrl+L in presenter view to toggle the laser. The audience and presenter windows stay synchronized in both directions without a server.

For a phone remote on the same Wi-Fi, run:

npm run present

Select the R control and scan the QR code. The phone starts with large navigation controls, a current-slide preview, and a touch laser surface. Select Presenter view on the phone to see speaker notes, the next-slide preview, timer, and page selector; Simple remote returns to the touch-friendly controller without losing the pairing session. FrameSeq relays navigation, reveal steps, and pointer coordinates through the local presentation server—no account, internet connection, or cloud service is required.

You can also append ?presenter=1 to the audience page's current URL. For example, if the audience page is running on port 5174:

http://localhost:5174/?presenter=1

Use the port printed by npm run dev; Vite does not always use 5173 when that port is occupied.

Same-device synchronization uses BroadcastChannel; phone pairing uses a local WebSocket only while npm run present is active. See Presenter view.

Advanced object API

The linear authoring layer is recommended for most slides. The low-level UI layer is available for special compositions:

import { Column, Image, Row, Slides, Text } from "@pride7/frameseq";

const slides = Slides("Custom layouts");

slides.slide("Architecture").custom(
  Row(
    Image("diagram.png"),
    Column(
      Text("Compiler").size(32).bold(),
      Text("DSL → HTML → PDF").size(20),
    ).gap(16),
  ).gap(40),
);

export default slides;

This layer includes SlidesRoot, Slide, Row, Column, Stack, Text, Image, Code, Equation, and chainable modifiers.

Documentation

Continuous integration and changelog

Every push and pull request runs the complete release:check suite on GitHub Actions: types, themes, desktop and mobile browser behavior, layout diagnostics, remote control, Gallery, static and single-file HTML, PDF, editable and flattened PPTX, and installation from packed npm tarballs. A separate least-privilege workflow builds the live Gallery and deploys it to GitHub Pages only from main.

User-visible changes are recorded in CHANGELOG.md. Add new work under Unreleased, then move it into a dated semantic-version section when publishing.

Development

Before publishing a FrameSeq release locally:

npm run release:check

The release check builds the library, demo, and Gallery, runs browser tests at desktop and mobile viewport sizes, packs and installs the npm tarballs in a clean project, type-checks the generated document, builds static HTML, and exports PDF and PPTX files.

Maintainers can instead publish both npm packages from a matching version tag through GitHub OIDC. See Release FrameSeq for the one-time Trusted Publisher configuration and tag workflow.