Editable slide decks from any content, as one self-contained HTML file. A Claude Code and Codex skill with a plain-Node CLI: the deck is a JSON model, the file is the editor, and a verifier checks the layout before a human ever sees it.
Open-source alternative to PowerPoint, Keynote, Gamma and the PPTX-generator skills for coding agents. Next to frontend-slides-style HTML skills it adds in-place editing, an enforced brand type scale, an in-file PDF writer and a layout gate. Zero dependencies, zero network, MIT.
Try it live: https://grunion-ai.github.io/decklet/deck.html (drag, retype, ⊞ contact sheet, ⤓ PDF, F fullscreen). The template pack: https://grunion-ai.github.io/decklet/library.html — every template and layout the engine ships (58 + 22), one per slide, grouped by the kind of slide it is. If decklet builds you a deck, star the repo: stars are how agents and people find it.
What's next: ROADMAP.md covers speaker notes, mobile, document formats and export targets, with the sequencing and the rejected options.
Node ≥ 22, nothing to npm install. Install it as a skill, or clone it anywhere and call the CLI.
# Claude Code plugin (marketplace + plugin live in this repo)
claude plugin marketplace add grunion-ai/decklet
claude plugin install decklet@grunion
# Any skills-aware agent (Claude Code, Codex, Cursor, Amp, and the rest)
npx skills add grunion-ai/decklet
# Or by hand: SKILL.md is picked up on the next session
git clone https://github.com/grunion-ai/decklet ~/.claude/skills/deckletThen, in any session: "make a deck from these notes".
# As a CLI
git clone https://github.com/grunion-ai/decklet && cd decklet
node bin/validate.mjs examples/explainer/model.json
node bin/create.mjs --model examples/explainer/model.json --out deck.html --title decklet
open deck.htmlWorks with Claude Code, Codex, Cursor and any agent that can run Node: the skill is SKILL.md, the toolchain is four Node scripts.
| decklet | HTML slide skills (frontend-slides and kin) | PPTX skills (python-pptx, html2pptx) | Slidev, Marp | Gamma and hosted generators | |
|---|---|---|---|---|---|
| Output | One .html, editable in place |
HTML page(s) | .pptx |
Markdown source plus a build | Hosted app |
| Edit after generation | Drag, retype, resize, reorder, undo, all inside the file | Regenerate (a fork adds drag editing) | PowerPoint | Edit the markdown, rebuild | In the app |
| Brand type scale enforced | Eight roles, no per-element size overrides, gate-checked | Theme CSS | Template | Theme | Themes |
| Layout checked before hand-off | verify: parity, collisions, pixel diff against references |
No | No automated gate | No | No |
| Runtime dependencies | None | None | Python and libraries | Node toolchain | Docker or cloud |
| Network when opened | None | Webfonts, usually | n/a | Dev server for Slidev | Yes |
| Written inside the file, no dependencies | Browser print | Export | Playwright or Chrome export | Export |
As of September 2026; check each project for current terms. Pick decklet when an agent drafts the deck and a person finishes it by hand. Pick a PPTX skill when the deliverable has to be a .pptx, and Slidev or Marp when the author writes markdown by hand.
A slide engine where the deck is a JSON model and the renderer is the editor. create turns model.json into one self-contained deck.html: open it from disk, drag and retype in place, present fullscreen, print to PDF. No install, no server, no network request, ever.
Who it is for. Agents (Claude Code, Codex, Cursor, any tool-using model) that must turn content into a deck a human will then edit. Humans get a file they can open and change; agents get a contract they can validate before a browser is involved.
Inputs → outputs.
- in: any content (outline, notes, markdown, transcript, data) + a format (
slides,carousel,document-letter,document-a4) + a style (brand tokens + eight text roles, or the neutral default) - out:
deck.html— one file, ~40 KB (this repo's explainer carries three inlined screen clips, so it weighs ~270 KB), editable, printable, verifiable
Zero dependencies. The engine is plain HTML/CSS/JS in a single file. The CLI is plain Node ≥ 22. Playwright is an optional devDependency used only by verify, pdf and import-html; nspell + dictionary-en are optional devDependencies used only by create to flag misspellings (absent, create says so and the editor falls back to the browser's own checker).
One file. The model, the styles, the renderer and the editor ship inside the deck. Copy it, email it, commit it.
Read SKILL.md — it is the product. It defines the inputs, the process, the model and style contracts, verification thresholds, anti-patterns and three worked examples. The whole loop:
node bin/validate.mjs model.json # contract check, no browser
node bin/create.mjs --model model.json --style style.json --out deck.html --format slides
node bin/verify.mjs deck.html [--refs shots/] # layout parity always; AE diff when refs exist
node bin/pdf.mjs deck.html [out.pdf] # vector PDF, one slide per page at slide size; self-gating
node bin/edits.mjs deck.html # what the human changed in the editor (read before revising)
node bin/create.mjs --model model.json --out deck.html --from deck.html # revise: keep ids, replay human edits (human wins), keep versions
node bin/import-html.mjs --w 1600 --h 900 --out model.json 'pages/*.html' # finished HTML → model
llms.txt is the machine summary and file map. deck.html is the engine explaining itself — twelve slides built from examples/explainer/model.json by the same CLI, including the motion vocabulary and three GIF clips of the editor filmed from the deck itself.
- Single file. Model + renderer + editor in one
.html; nothing is fetched at runtime. - Zero network. No webfonts, CDNs or remote images; images are data: URIs. The gate greps for it.
- Editable. Drag, ⌘-multi-select, marquee select (drag a window on empty canvas), double-click to retype, corner-nib resize, point nibs on connectors, floating role/mark/colour toolbar (B/I/U/S, sub/sup, the deck's own swatches; ⌘B/⌘I/⌘U), undo that survives reload, contact sheet with grab-and-drag reordering.
- Human edits are never lost. Every edit lands in an in-file log and, with ⌘S in Chrome/Edge, in the deck file itself (File System Access — asked for once).
create --fromreplays the log onto the next version, human wins, conflicts flagged; the file keeps a version history you can restore from.bin/edits.mjsprints the log for an agent. - Motion, honestly. Four entrance anims —
rise,fade,pop,wipe— staggered 120 ms on slide entry only.prefers-reduced-motionturns them off, and print, the contact sheet, the PDF andverifyall draw the settled frame, so motion can never change what is measured or exported. - Brand-true. Eight text roles (Title, Supertitle, H1, H2, Body, Caption, Label, Stat) are the only source of font/size/leading; rows cannot override them. Chrome is one deck-wide master layer on a
margintoken. Tokens re-theme every deck. - PDF.
⤓writes a true slide-sized PDF inside the file (foreignObject → canvas → JPEG → PDF, zero dependencies);⌘Pis the paper path with named Letter/A4 pages (Safari-safe), one page per slide. Safari's in-file raster path is unconfirmed — it falls back to print. - Verified.
validate(pure Node) +verify(layout parity in a real browser, AE pixel diff against references). A deck that fails parity is not done.
{
"w": 960, "h": 540,
"layouts": { "content": { "supertitle": { "x": 60, "y": 52, "w": 840, "role": "Supertitle" },
"title": { "x": 60, "y": 76, "w": 840, "role": "H1" } } },
"master": [ { "id": "foot", "footer": 1, "x": 660, "y": 500, "w": 240, "align": "right", "role": "Label", "text": "decklet" } ],
"slides": [ { "layout": "content", "els": [
{ "slot": "supertitle", "text": "THE MODEL" },
{ "slot": "title", "text": "Every slide is rows in a JSON array." },
{ "x": 60, "y": 170, "w": 195, "h": 110, "tile": 1, "role": "Stat", "text": "1,240" },
{ "x": 120, "y": 340, "w": 70, "h": 60, "bg": "#2F4E7A", "bar": 1 },
{ "x": 730, "y": 200, "w": 150, "donut": 72 }
] } ]
}A row is text by default; box, tile, bar, line, donut, svg, img are props on the same row. Roles come from styles.roles (or style.json); a row may set weight/colour/case, never size. Full reference: SKILL.md → MODEL CONTRACT.
| capability | status | notes |
|---|---|---|
slides 16:9 (960×540, 1600×900) |
supported | editing, contact sheet, present, PDF, verify |
carousel 1:1 / carousel-4x5 |
experimental | sizing, editing, PDF work; no per-card PNG export |
document-letter / document-a4 |
experimental | page = canvas, print zoom 1; text does not flow across pages |
| drag / multi-select / resize / undo | supported | undo history persisted per deck; connectors get point nibs |
| edit log · write-back · versions | supported | in-file /*LOG*/ + /*VERSIONS*/; ⌘S writes the file (Chrome/Edge), create --from migrates, restore from the history popover |
| inline text editing + B/I/U/S + colour runs | supported | stored as html on the row |
| roles · slots · master layer · footer counter | supported | see SKILL.md |
| bars, lines, donuts, tiles, boxes | supported | one row each, no SVG layer |
| SVG / raster images | supported | inline svg, data: img |
| entrance motion | supported | anim: rise|fade|pop|wipe, 120 ms stagger on slide entry, respects reduced-motion, never in print/PDF/parity |
| animated GIF clips | supported | img data: URI plays as-is; docs/record-clips.mjs films them from the deck and writes them back into the model |
| contact sheet (select, reorder, dup, delete) | supported | 3-across live thumbnails; pointer-drag reorder (mouse + touch), also in present mode |
| fullscreen presentation | supported | F / ⛶, hover-peek HUD (pinned while a menu or the sheet is open) |
| supported | ⤓ → slide-sized PDF written in-file (Chromium verified; Safari unconfirmed → print fallback); ⌘P → paper, Letter/A4 named sizes | |
| HTML pages → model | supported | bin/import-html.mjs (Playwright) |
| validate / verify | supported | parity mandatory, AE optional |
| PPTX / Google Slides export | no | out of scope |
| presenter view, notes | roadmap |
deck.html the engine + the explainer deck (built from examples/explainer)
template.html the engine with an empty model — create.mjs fills it
bin/validate.mjs model contract, pure Node
bin/create.mjs model (+style) → deck.html; format presets
bin/verify.mjs parity + AE + contract proof (Playwright optional)
bin/import-html.mjs finished HTML pages → model.json (Playwright)
SKILL.md the agent authoring skill
llms.txt machine summary
examples/ explainer, quarterly-update, launch-carousel, one-pager (brief → model + style)
test/gate.test.mjs engine + validator + create + import + live proofs
npm test # node --test; live browser tests skip when Playwright is absent (CI installs Chromium, so they run there)
npm run test:live # same, after: npm i -D playwright && npx playwright install chromium
Full plan, with sequencing, sizes and rejected options: ROADMAP.md.
Now: contract hygiene, the slide.notes and row.alt model fields, notes as data, phone reading, new format presets, a PNG export CLI.
Next: presenter view, searchable PDF, aspect-aware layouts, phone presenting.
Later: document text flow, PPTX export, touch editing, a slide revision store.
MIT © Grunion
