Ask Claude, ChatGPT, Copilot or any coding agent for a presentation, then move things around yourself. Lectern is a drag-and-drop editor for HTML slide decks: the assistant writes the file, you fix the layout on a canvas, and each of you sees the other's changes within a second.
npx lectern-editor new my-talk --title "Why whales lost their legs" # scaffold a deck
npx lectern-editor my-talk/index.html # open the editorThe .html file is the deck — a reveal.js presentation. There is no
proprietary format, no import and no export step: it opens in any browser, prints to PDF, and
lives in git like any other file. Lectern writes back only the slides you changed, byte for byte,
so git diff stays readable and your assistant can keep editing the same file while you have it
open.
- Nothing leaves your machine. No account, no telemetry, no hosted version, no runtime dependencies. Works with the wifi off — the tests fail if any part of the editor so much as tries to reach the network.
- One file, no install: double-click
Lectern.html(a single self-contained page, fromfile://). Ornpx lectern-editorfor Firefox and Safari, which have no folder picker. - Works on decks you already have, and on any hand-rolled page of
<section>slides. - Five-minute tutorial:
public/tutorial.html(the editor's Help menu opens it).
Made for people who like the slides an LLM writes but not the layout it leaves behind: a PowerPoint- or Keynote-shaped canvas over plain HTML, with the assistant still in the loop.
Ask your assistant for a deck; it writes an HTML file. Open that file in Lectern and fix the layout by hand while
the assistant keeps working on the content. Where you want something changed, press N on the slide and write
it there — "draw a whale here", "this is too dense, split it". The note is saved in the HTML; the assistant
lists the notes, does them, and marks each one done (it turns green). Both of you edit the same file, and each sees
the other's changes within a second.
To make an assistant fluent in this, give it AGENTS.md — one document with the deck format, the
class vocabulary, the notes protocol and the editing rules:
-
Claude Code:
/plugin marketplace add calumhrmurray/lecternthen/plugin install lectern@lectern— adds aslidesskill that is picked up whenever you ask for a presentation. Or copyplugins/lectern/skills/slides/into~/.claude/skills/. -
Codex, Cursor, Copilot, Gemini CLI, …: they read
AGENTS.mdfrom the working folder — copy it into your project, or paste this into the chat:Read AGENTS.md from https://github.com/calumhrmurray/lectern and make me a slide deck about … in a folder called
talk. -
Any agent with a shell:
npx lectern-editor guideprints the same document;npx lectern-editor new talkscaffolds a deck;npx lectern-editor notes talk/index.htmllists what you asked for on the slides.
- Canvas editing — click to select, drag to move with snapping guides (slide edges, centre lines, other objects' edges), 8-handle resize (images keep their aspect ratio), rotate, marquee and shift-click multi-select, arrow-key nudging, align / distribute / z-order.
- Text in place — click any text and press
⏎(or just type over it); bold/italic/underline/lists/links,Tabto indent list items, LaTeX kept as source and re-typeset by the deck's KaTeX/MathJax on commit. - Layout-aware — elements laid out by your CSS are nudged (
position: relative) so nothing else jumps; new objects are free-floating (position: absolute). Switch either way from the inspector. - The quiet room (the default view) and the map — the slide gets the whole window on an off-white
background (click the background for the dark room, click again to come back), with the neighbouring
slides showing at the edges: the previous and next along the sides, and a vertical stack's slides above
and below, on the axis the arrow keys would actually take. Along the bottom — in every view, not just this one — a compass (one dot per
slide, a descender under any slide with a vertical stack, a seam between sections) and glyphs for the
map, present and notes for AI; on the right, the page number and one button that summons the tools.
Mopens the map — the deck laid out the way reveal walks it, sections left to right and stacks downward — where you add and title slides, reorder them, name sections (data-section) and drop one slide under another to make it a sub-slide.Qbrings the toolbar, navigator and inspector back for good; holdSpacefor a look at them. - Insert — text, headings, bullet lists, images (copied into the deck folder), shapes (rectangle, ellipse, outline, line, arrow, callout), tables, code, equations, web embeds.
- Slides — thumbnails rendered with the deck's own stylesheet, drag to reorder, add from
layouts, duplicate, delete, copy/paste, vertical stacks,
data-visibility(backup slides). - Inspector — position/size, typography, fill/border/shadow, the deck's own CSS classes
as one-click toggles (
.kicker,.tide,.fig…), reveal fragments (effect + order), slide backgrounds and transitions, speaker notes, raw HTML of a slide. - Notes for an AI — press
N(or Note for AI) and write on the slide what you want done there: "draw a whale here", "explain the 1905 law here". Notes are saved in the HTML as<div hidden data-ai-note style="position:absolute;left:…;top:…">…</div>— invisible when presenting, visible as sticky notes while editing, positioned in slide coordinates. The Notes for AI panel lists them all and copies a ready-made prompt;npx lectern-editor notes deck.htmlprints the same from a terminal. Tell Claude Code "do the notes in index.html": it acts on each, appends its reply as<p data-by="ai">…</p>and marks the notedata-ai-note="done"— it turns green. A note is a thread like a document comment: click it to add a comment (a green one turns yellow again), double-click a green one to dismiss it. - Autosave (on by default; toggle in the menu): edits reach the file a second after you make them, so an assistant watching the folder sees your notes as you write them.
- Undo/redo everything, including across saves.
- Multi-file decks — a shell whose
<div class="slides">is filled from part files (const parts = ['slides/p0.html', …]or<div class="slides" data-parts="a.html b.html">) is edited as one deck; each slide is saved back to the file it came from, and slides moved across files take their<!-- comment -->with them.
The whole editor as one self-contained file. Build it with npm run build, which writes Lectern.html here, and
open it from file://: no server, no port, no network — the browser is only there to render HTML. On a Mac, sh scripts/make-app.sh builds Lectern.app,
a launcher you can drag to the Dock; it opens Lectern.html in Chrome. Otherwise:
- Open
Lectern.htmlin Chrome, Edge or another Chromium browser. - Click Open a folder… and pick the folder that contains your deck; choose the HTML file if the folder has several.
- Edit.
⌘Swrites straight back to the file on disk.
Nothing leaves your machine. The deck's stylesheets, scripts and images are read from the
folder you picked. Keep Lectern.html anywhere you like — it is a single file.
Working alongside an assistant. The editor watches the deck's files. If Claude Code (or you, in
a text editor) changes a file while the editor has no unsaved edits, the canvas reloads by itself and
stays on the same slide. With unsaved edits, a banner offers to reload or keep yours, and saving never
overwrites a newer file without asking. So: keep Lectern open, ask the assistant for changes, watch
them appear; drag things around, ⌘S, and the assistant reads your version.
npx lectern-editor deck.html # serve the editor for a deck (or a folder) and open the browser
npx lectern-editor new my-talk # scaffold a deck: index.html, theme.css, reveal/ (--title --author --theme --size)
npx lectern-editor notes deck.html # list the pending notes for an AI, as a prompt
npx lectern-editor guide # print AGENTS.md, the instructions for assistantsThe server is a tiny local one (127.0.0.1:8765); it works in Firefox and Safari too, which lack the folder picker.
Needs Node 18+. Any static host can also serve dist/ (HTTPS is required for the folder-serving worker).
New deck… creates a folder with index.html, a theme.css and a copy of reveal.js (so it
works offline). You choose slide size, author and one of four built-in themes — Paper (warm,
serif), Ink (dark), Academic (white, tight), Aquarelle (blue watercolour washes) — all
sharing one class vocabulary (.kicker, .accent, .card, .cols/.col, .title-slide, .break…)
so slide layouts and inspector chips behave the same everywhere.
The welcome screen lists two complete, referenced decks. Save to a folder… copies one (with its own reveal.js) into a folder you choose and opens it from there — from then on it is an ordinary HTML deck on your disk; Preview opens a throw-away copy in memory.
- From land to sea — the evolutionary pathways of whales (English, Aquarelle theme): 15 slides
from raoellids and Pakicetus to baleen-whale gigantism and genome-level gene loss. Every slide has a
data-refattribute and full citations in its speaker notes; the last slide lists the sources. - Préparer l'entretien de naturalisation (French, Paper theme): 17 slides on the assimilation interview — legal framework (Code civil art. 21-24 etc.), procedure and deadlines, symbols, values, institutions, key dates, rights and duties — with the relevant texts cited on each slide. Rules in this area change; the deck states its verification date and says what to check on service-public.fr.
They live in public/examples/; add a folder and an entry in public/examples/index.json to ship
your own.
- reveal.js:
<div class="reveal"><div class="slides">…</div></div>and a globalReveal(the usual<script src="…/reveal.js">+Reveal.initialize({...})). Vertical stacks, fragments, backgrounds, transitions and speaker notes are all edited. Decks that useimport Reveal from 'reveal.esm.js'must expose the instance aswindow.Reveal. - Plain HTML: any page whose slides are
<section>elements in a container, driven by your own script (e.g.#deck > section.slidewith an.activeclass and#3-style hashes). Lectern learns the deck's "active" class from whichever slide is showing, shows slides itself while you edit, keeps the deck's keyboard/click handlers from interfering, and renders the page in a fixed viewport (1280×720 by default, changeable in the Deck tab) sovw/vhlayouts look as they would on a projector. reveal-only features (backgrounds, transitions) are hidden.
⌘S save · ⌘Z / ⌘⇧Z undo/redo · double-click / right-click leave a note for AI · ⏎ edit selected text · Esc stop editing / deselect ·
⌘C/X/V/D copy/cut/paste/duplicate · ⌫ delete · arrows nudge (⇧ 10 px) ·
⇧-drag constrain / keep aspect · ⌥-drag ignore snapping · PgUp/PgDn slides ·
⌘⇧N new slide · ⌘]/⌘[ forward/backward · typing with a text object selected edits it ·
N note for AI at the pointer · M map · Q quiet mode · Space (hold) show the chrome · + − 0 zoom · ? full list.
npm install
node test/e2e/prepare.js # builds the demo deck in test/.tmp/demo (reveal + KaTeX included)
npm run dev # http://localhost:5173/?ws=local — edits the demo deck with hot reload
LECTERN_DIR=~/talks/iap npm run dev # …or any folder / deck.html
npm test # unit tests (vitest)
npm run build # dist/ (+ copies reveal.js into public/reveal)
npm run test:e2e # build + Playwright end-to-end tests through the CLI server
node cli/index.js test/.tmp/demo # the CLI on the demo deckLayout of the source:
src/deck/ document model: HTML scanner, splicing serialiser, history, templates, theme-class discovery
src/stage/ the canvas: iframe + reveal control, src↔live mirroring, overlay, pointer interactions, text editing
src/app/ Editor (all user operations as undoable transactions) and App (workspaces, saving, shortcuts)
src/ui/ toolbar, navigator + thumbnails, inspector, panels, dialogs, menus
src/workspace/ file access: File System Access API (+ service worker), CLI server, in-memory demo
cli/ `lectern` command: static server for dist/ plus read/write of the deck folder
public/sw.js service worker that serves a folder handle at /fs/<id>/…
A deck runs in an iframe on the same origin as the editor, by design: the editor reaches into the deck's DOM to select,
move and retype things, and the deck's own scripts (reveal, KaTeX, a custom driver) must run for the canvas to look right.
The corollary is that a deck's scripts can reach the editor too — open only decks you trust, exactly as you would any HTML
file. The window.lectern handle to the App is only exposed in dev builds and when the URL carries ?test=1 (the end-to-end
tests use it). The CLI server binds to 127.0.0.1, refuses requests whose Host or Origin is not the machine itself
(so a DNS-rebinding page cannot read or write the folder) and refuses symlinks that lead outside the deck folder; --host
on any other address prints a warning that the folder is open to the network. Every response carries
X-Content-Type-Options: nosniff, and the editor's own pages are served under a Content-Security-Policy of
default-src 'self' — deck files are not, because your slides may point wherever you like.
No network, checked by the tests. Lectern has no runtime dependencies, no telemetry and no hosted version;
reveal.js and KaTeX are copied into the deck folder so a deck with maths typesets offline. test/e2e/offline.spec.ts
cuts the network at the browser and fails if any request tries to leave the machine — the editor, the welcome
screen, a scaffolded deck, and a deck opened from Lectern.html over file://. The one thing that reaches the
internet is lectern new --no-reveal, which you have to ask for by name: it points the deck at a CDN instead of
copying reveal.js next to it.
DeckDocument keeps the original text and a parsed DOM. Every edit touches the DOM only. On save,
a small tolerant scanner locates each top-level <section>'s byte range in the original text;
sections whose outerHTML is unchanged are copied from the original bytes, modified ones are
re-serialised, and each section carries the text that preceded it (its comment). Inline styles are
patched textually so #4a7bd0 never turns into rgb(74, 123, 208).
The deck runs unmodified in an iframe (reveal is reconfigured for editing: no keyboard/touch, no transitions, every fragment shown). The source DOM is the truth; the live DOM is a rendering. Both receive identical structural edits, so an element is located in the other tree by its index path from the top-level section — plugin rewrites (KaTeX, highlighting) happen below the elements you select and never disturb the paths. Undo is a whole-section snapshot restore.
npm publish ships the CLI, dist/ (the built editor, Lectern.html, reveal.js and KaTeX) and AGENTS.md as the
lectern-editor package; prepublishOnly runs the build. There is no hosted editor and there is not going to be
one: Lectern is a local tool, and a page on someone's server is the opposite of that. dist/ is a static site if you
want to serve it yourself on a machine you control (the service worker needs HTTPS or localhost).
Can I use this with ChatGPT / Copilot / Cursor / Gemini instead of Claude? Yes. Any assistant that can read a file
and write one works; give it AGENTS.md (or run npx lectern-editor guide). The Claude Code plugin is a
convenience, not a requirement.
Do I need an API key or an account? No. Lectern is an editor, not an AI client — it never talks to a model. Your assistant edits the file with its own tools, wherever it already runs.
Does it work offline? Yes, entirely, and the test suite enforces it.
Can I edit a reveal.js deck I already have? Yes — npx lectern-editor path/to/deck.html. Lectern renders it with
the deck's own theme and plugins and writes back only what you change. Plain HTML decks (a page of <section>s with
your own script) work too.
How do I get a PDF? reveal's print mode: open index.html?print-pdf in Chrome and print. Every slide is one page.
What happens to my deck if I stop using Lectern? Nothing. It is an ordinary reveal.js HTML file that you can open, present and edit by hand; Lectern leaves no runtime attributes and no lock-in behind.
MIT. reveal.js is © Hakim El Hattab and contributors, MIT. Lectern is an independent project and is not affiliated with any presentation-software vendor.