Skip to content

Using the command line

The lattice command turns a Markdown deck into a PDF, a PowerPoint, images, or a web page. This page is a set of recipes: find the task, copy the command, change the file names. Every flag is listed in the CLI reference.

The one rule to learn first: the output file’s extension picks the format.

Terminal window
npx lattice deck.md deck.pdf

Change deck.pdf to deck.pptx and you get a PowerPoint instead. Everything else is an option you add after the two file names.

You need Node 22.13 or newer. Install Lattice into your project:

Terminal window
npm install @laticent/lattice

Or clone the repository, which also gives you the helper scripts in Check a deck before you render it:

Terminal window
git clone https://github.com/Laticent/lattice.git
cd lattice
npm install

Either way, npm install downloads the headless Chromium that Lattice renders with. The render itself needs no network and no account.

Every command on this page runs the copy installed in your project, which is what npx lattice finds. Outside a project, use npx @laticent/lattice (or install it globally with npm install -g @laticent/lattice and type lattice): the unscoped lattice package on npm belongs to someone else, so a bare npx lattice in an empty folder downloads that package instead.

To check it works, ask for the version:

Terminal window
npx lattice --version

npx lattice --help prints one screen: the output formats and the options most decks need. npx lattice --help all prints every option, the same list as the CLI reference.

With lattice on your PATH (a global install, or npx lattice replaced by a shell alias), one line makes Tab complete subcommands, every option, the values an option takes (palettes, --size canvases, --paper sheets, plugin names) and the packages you installed:

ShellAdd this line
bash (3.2 or newer)eval "$(lattice completion bash)" in ~/.bashrc
zshsource <(lattice completion zsh) in ~/.zshrc, after compinit
fishrun mkdir -p ~/.config/fish/completions && lattice completion fish > ~/.config/fish/completions/lattice.fish once
PowerShelllattice completion powershell | Out-String | Invoke-Expression in your $PROFILE

lattice completion <shell> only prints a script; it never edits a file. Paths still complete as before, narrowed to the files that fit: .md for the deck, the output formats for the output. Open a new shell after you reinstall Lattice somewhere else, so the script finds the new copy.

Terminal window
npx lattice deck.md deck.pdf

The PDF keeps real text: you can select it, search it, and copy from it. The first render of a session takes longer while Chromium starts.

Every format except .html also writes an HTML copy beside the output (deck.html here). You can open it in a browser or delete it.

A deck’s size: sets its canvas. To render one deck on a different canvas without editing it, pass --size. Try square, portrait, story, mobile-landscape or 4K:

Terminal window
npx lattice deck.md square.pdf --size square
npx lattice deck.md story.png --size story

The deck’s front matter usually names its palette (theme: cuoio). To render the same deck in a different palette without editing it, name one on the command line:

Terminal window
npx lattice deck.md deck.pdf cuoio

The same thing with a named flag, which reads better in scripts:

Terminal window
npx lattice deck.md -o deck.pdf -p cuoio

To see every palette you can name:

Terminal window
npx lattice packages list --type theme

A palette on the command line beats the LATTICE_PALETTE environment variable, which beats the deck’s theme:. The default is indaco.

Terminal window
npx lattice deck.md deck.pptx

Each slide becomes one full-bleed image, so the PowerPoint looks exactly like the PDF. The trade-off is that the text is not editable in PowerPoint.

To edit the text, add --editable. Every paragraph becomes a real text box in the deck’s font, over a picture of the slide with the text removed:

Terminal window
npx lattice deck.md deck.pptx --editable

Charts, diagrams and equations stay part of the picture. The .pptx carries the deck’s fonts, but an app that ignores embedded fonts (LibreOffice, Keynote, Google Slides) shows the deck’s fonts only where they are installed. Without them every word is measured in a substitute font, so lines run wider or narrower than the slide’s picture, and a small picture set inside a sentence (an icon, a spark or an avatar) can sit off its words. Install the deck’s fonts, or use the picture .pptx or the PDF when the layout must hold exactly.

Terminal window
npx lattice deck.md deck.odp # one picture per slide
npx lattice deck.md deck.odp --editable # real text boxes, fonts embedded

Lattice writes the .odp itself, so you do not need LibreOffice to export. The editable .odp carries the deck’s fonts, so it looks the same on any machine with LibreOffice Impress.

One PNG per slide, numbered deck.001.png, deck.002.png, and so on:

Terminal window
npx lattice deck.md deck.png

For JPEG or WebP, or for thumbnails and chart files in one download, ask for a .zip image set:

Terminal window
npx lattice deck.md slides.zip
npx lattice deck.md slides.zip --image-format webp --image-size 1x
npx lattice deck.md slides.zip --image-format jpeg --image-quality 80 --no-svg

By default the zip holds full-size PNGs, a thumbnails/ folder, and an assets/ folder with each chart and diagram as its own SVG. There is no loose .jpg or .webp output. Asking for deck.webp stops with an error that names the .zip command above.

Print mode draws the deck in black ink on white, and gives chart series hatching and dot patterns so they still read in grayscale:

Terminal window
npx lattice deck.md handout.pdf --print

To fit each slide onto office paper with a safe margin, add --paper. Use auto to let Lattice pick the sheet, or name letter, legal or a4:

Terminal window
npx lattice deck.md handout.pdf --print --paper a4
npx lattice deck.md handout.pdf --paper letter --orientation portrait

--paper pages are images, so the text is no longer selectable.

--present marks the PDF to open full screen in Acrobat and most desktop viewers, with a soft fade between slides. You still advance the slides yourself:

Terminal window
npx lattice deck.md talk.pdf --present

The player is a single .html file with everything inside it. It works offline, opens with a double-click, and offers three views: Present, Read Slides and Read Article.

Terminal window
npx lattice deck.md deck.html --player

Send the player, not a plain .html. A plain .html (and the one written beside every PDF) keeps any <script> or onerror= in your deck live, so it runs in whoever opens it, and the CLI warns when a deck has any. The player drops both.

Your speaker notes travel with the player so that you can present from it. If you are sending it to someone else, strip them first:

Terminal window
npx lattice deck.md deck.html --player --strip-notes

The player shows a deck’s motion when the deck turns it on. To ship still slides in a file you forward, add --no-player-motion.

To open the player in a set mode, whatever the deck says, add --player-mode light, dark or system. To give it a spoken voice-over, add --narrate. It uses the same optional voice install as a narrated video:

Terminal window
npx lattice deck.md deck.html --player --player-mode dark
npx lattice deck.md deck.html --player --narrate --strip-notes

A player loads nothing from the web, so a picture from a web address ships as a placeholder. To let the reader choose, name the sites with --player-consent. The player then shows a banner that names them, and loads their pictures only when the reader presses Load pictures. It asks again each time the file opens. With JavaScript off nothing loads, and the banner lists each picture as a link instead:

Terminal window
npx lattice deck.md deck.html --player --player-consent https://images.example.com

Only pictures placed with ![](…) are offered. A web background (![bg](…)) stays a placeholder, and the run says so. The site must allow pages to read its pictures from another origin (it sends an Access-Control-Allow-Origin header). If it does not, the banner says the pictures could not be loaded and lists their links. To put the pictures in the file itself instead, so the reader is never asked, export with --allow-remote: the player then embeds every web picture it can fetch.

--read writes the deck as a web article instead of a slide stack: real headings, paragraphs and lists, with charts and tables as figures. Reader modes and “summarize this page” tools can read it.

Terminal window
npx lattice deck.md article.html --read

--fluid writes a viewer where each slide fills the screen and reflows to portrait on a phone. Readers swipe between slides.

Terminal window
npx lattice deck.md deck.html --fluid

If you pass both --fluid and --player, --player wins.

--embed-source attaches the deck’s Markdown to the PDF. Anyone with the PDF can re-render the deck. The source includes your speaker notes, so combine it with --strip-notes if the notes are private:

Terminal window
npx lattice deck.md deck.pdf --embed-source
npx lattice deck.md deck.pdf --embed-source --strip-notes

--reopenable puts the whole deck inside a .pdf or .pptx as a .lattice project. Whoever you send it to opens the Studio, picks Import deck… in the deck switcher, and gets the deck back to edit, exactly as you wrote it. It is the same file the Studio’s “Re-openable in Lattice” switch writes, so the two tools’ exports open the same way.

Review comments never go in. Hidden slides do, and so do speaker notes unless you add --strip-notes. A theme or component you installed with lattice packages add travels with the deck.

The deck travels as written, so its theme is the one in its front matter. If you theme the export with -p instead, the PDF shows that theme but the deck re-opens in its own, and the CLI warns you. Put theme: <name> in the deck to make the theme travel.

Terminal window
npx lattice deck.md deck.pdf --reopenable --strip-notes
npx lattice deck.md deck.pptx --reopenable

A plain HTML comment on a slide is that slide’s speaker note:

<!-- Open with the one number that matters. -->

Notes ride inside every PDF, hidden. Three switches change that:

Terminal window
# Write the notes to deck.notes.txt as well
npx lattice deck.md deck.pdf --notes
# Show a clickable note icon on every slide that has one
npx lattice deck.md deck.pdf --notes-icon
# Remove the notes from every copy: the PDF, the HTML and any embedded source
npx lattice deck.md deck.pdf --strip-notes

--captions writes WebVTT read-along captions: one deck.vtt for the whole deck, plus one deck.01.vtt per slide. Each slide reads its own content unless you wrote a <!-- say: … --> for it.

Terminal window
npx lattice deck.md deck.pdf --captions

--strip-say removes the spoken lines you wrote yourself (say:) and keeps the generated narration. It also removes the retired caption: comments and captions: map, including a comment that starts with Caption:, and says so when it does. It does not touch any other speaker note, and --strip-notes does not touch say: lines.

These commands run from a clone of the repository.

The linter finds authoring mistakes in about a second, with no browser. Each finding names the slide, says what will go wrong, and shows the fix.

Terminal window
npm run lint:deck -- deck.md

The options, one per line:

Terminal window
npm run lint:deck -- 'decks/*.md' # every deck matching a glob (keep the quotes)
npm run lint:deck -- --strict deck.md # warnings fail too
npm run lint:deck -- --fix deck.md # apply every automatic fix, in place
npm run lint:deck -- --json deck.md # machine-readable, for scripts and agents

The linter exits with 1 when it finds an error (or any finding, with --strict), so it can guard a CI job or a commit hook.

new:slide prints a working example of any layout, ready to paste into a deck:

Terminal window
node tools/new-slide.js --list # every layout, grouped by what it is for
node tools/new-slide.js kpi # a kpi slide, printed to the terminal

The skeleton starts at the layout comment, so add a --- line before it when you append it to a deck:

Terminal window
{ printf '\n---\n\n'; node tools/new-slide.js kpi; } >> deck.md

A slide with more content than fits is clipped, and the render prints an OVERFLOW warning that names the page. To see the problem marked on the page itself, render a proof with the author marker:

Terminal window
npx lattice deck.md proof.pdf --overflow-marker author

The marker draws a red ring and an “Overflows” flag on each clipped slide. The default, reader, draws a calm “Content clipped” tag instead, and off draws nothing. The terminal warning prints at every level.

To make author your standing default, set LATTICE_OVERFLOW_MARKER=author in your shell.

The command renders one deck at a time, so loop in the shell. -q keeps the output to errors only, and Lattice creates the out/ folder if it is missing:

Terminal window
for f in decks/*.md; do
npx lattice "$f" "out/$(basename "${f%.md}").pdf" -q || echo "FAILED: $f"
done
Terminal window
for p in indaco cuoio ardesia; do
npx lattice deck.md "out/deck-$p.pdf" -p "$p" -q
done

Set LATTICE_PALETTE once and every render in that shell uses it:

Terminal window
export LATTICE_PALETTE=cuoio
npx lattice deck.md deck.pdf

Install a theme or component made in the Studio

Section titled “Install a theme or component made in the Studio”

Themes, components, finishes and motion made in the Studio are packages. Install one once, and any deck can name it:

Terminal window
npx lattice packages check brand.zip # test the package without installing it
npx lattice packages add brand.zip # install it
npx lattice packages list # everything shipped and installed

A theme named after a shipped one installs under a new name, and add tells you which. A package that carries code asks you to approve it during add; --trust approves it without asking. Replace an installed package with --replace, and remove one by type and name:

Terminal window
npx lattice packages add brand.zip --replace
npx lattice packages remove theme/brand

To share a package, export it to a zip. This works for shipped packages too:

Terminal window
npx lattice packages export theme/brand -o brand.zip

Lattice ships a small motion library: npx lattice packages list --type motion names the scenes. A deck never names a motion package. The Studio’s Library lists the shipped scenes on its Motion tab, and Insert copies one into the deck, so the deck still renders on a machine without it.

Packages live in ~/.lattice/packages. Set LATTICE_HOME to move the store, or pass --packages <folder> to use a different store for one run, renders included:

Terminal window
npx lattice deck.md deck.pdf --packages ./team-packages

Some components carry code. Lattice runs that code only after you approve it, in a browser with no network. A deck that uses an unapproved component stops with the component’s name and the command to approve it:

Terminal window
npx lattice packages trust component/org-chart # show the code, then ask
npx lattice packages trust component/org-chart --yes
npx lattice packages untrust component/org-chart

If the code changes, Lattice asks again. Run without a terminal, packages add cannot ask, so it installs the package unapproved; approve it afterward with trust.

When Chromium’s OS sandbox is off for that code, the approval prompt says so, with the reason and the fix, and every render repeats that it is off. If Lattice runs as root, as it often does in a container, the fix is to set CHROME_PATH to a Chromium that an unprivileged user can run. On Ubuntu 23.10 and later, AppArmor stops the sandbox of a browser it has no profile for, such as the Chrome that puppeteer downloads, even for an ordinary user. There the fix is an AppArmor profile that lets the browser create user namespaces, or setting the kernel.apparmor_restrict_unprivileged_userns sysctl to 0. Inside a Docker container, Docker’s default seccomp profile is what blocks the sandbox, whatever the host’s AppArmor says: start the container with a seccomp profile that allows user namespaces.

To write a component that carries code, see Writing a code package.

In a clone of the repository, packages new plugin scaffolds a plugin in lib/plugins/<name>/: a manifest, a render script, styles, docs and test fixtures. Then rebuild and run the plugin tests, as the command tells you:

Terminal window
npx lattice packages new plugin demo-plot
npm run build
npm run test:plugins

To write it somewhere else for review, add --dir. The plugin lands in <dir>/<name>/:

Terminal window
npx lattice packages new plugin demo-plot --dir ./review

lattice video turns a deck into an MP4 with a spoken voice-over and a caption track, plus a .vtt caption file beside it. It voices the deck with Kokoro, the same on-device voice the Studio uses, so you need no account and no key.

The voice is an optional install. The first video also downloads the voice model once (about 80 MB):

Terminal window
npm i --no-save kokoro-js@1.2.1 @breezystack/lamejs@1.2.7

Then render:

Terminal window
npx lattice video deck.md talk.mp4
npx lattice video deck.md talk.mp4 --mode dark --fps 60
npx lattice video deck.md phone.mp4 --size mobile-landscape # fills a phone held sideways
npx lattice video deck.md talk.mp4 --no-guide --no-captions

By default the video follows the narration with the Guide, the focus and gestures you see in Present mode. --no-guide leaves them out.

Already have a narrated export from the Studio (Share → Webpage, with Include narration audio turned on)? Pass the .html instead, and nothing is voiced again:

Terminal window
npx lattice video talk.html talk.mp4 --lead-in 2000 --outro 3000

Video needs a Chrome that can encode H.264, such as Chrome for Testing. Point CHROME_PATH at it if Lattice picks the wrong browser.

lattice fmt pretty-prints a deck’s fenced code blocks in place, with Prettier, ruff (Python) or sql-formatter (SQL), whichever reads the block’s language:

Terminal window
npx lattice fmt deck.md
npx lattice fmt deck.md --check # exit 1 if a block would change, for CI

A block whose @code{…} record says format=off keeps its text, and width=48 in a record sets that block’s line width. The formatters are optional dependencies; if one is missing, lattice fmt names the package to install.

By default a render stays off the network. An image or font from a web address shows as a placeholder, and the run tells you how many it left out. To fetch them:

Terminal window
npx lattice deck.md deck.pdf --allow-remote

Fetching an image tells its server that someone opened the deck. Local files and the bundled fonts, diagrams and math always work offline.

Lattice writes the PDF itself: one photo of each slide’s background, with the words, charts and links drawn on top as real text and shapes. If a viewer still gets something wrong, try these one at a time:

Terminal window
# One full-page image per slide. The most compatible, but the text is not selectable.
npx lattice deck.md deck.pdf --raster
# Print with Chrome's own PDF printer instead of Lattice's writer
npx lattice deck.md deck.pdf --chrome-pdf
# Keep SVG images as vectors (they are converted to 2x PNG by default)
npx lattice deck.md deck.pdf --keep-vector-images

Each render step has a 90-second watchdog. On slow hardware with a very large deck, raise it:

Terminal window
LATTICE_RENDER_WATCHDOG_MS=300000 npx lattice deck.md deck.pdf

From a clone, export:marp writes a self-contained bundle that renders with the recipient’s own marp-cli. It bakes the page splits into the Markdown and includes the themes, fonts and assets:

Terminal window
npm run export:marp -- deck.md deck-marp.zip
npm run export:marp -- deck.md deck-marp.zip cuoio

The export does not render, so it cannot find overflow. Render a PDF first to check.

What you seeWhat to do
error: palette not found: brandCheck the spelling against npx lattice packages list --type theme, or install the theme with packages add
error: unsupported output extensionUse .pdf, .pptx, .odp, .png, .zip or .html. For JPEG or WebP, use a .zip with --image-format
error: unknown optionCheck the flag in the CLI reference. Flags that take a value accept --flag value and --flag=value
The render says it cannot find a browserSet CHROME_PATH to a Chrome or Chromium on your machine
A web image shows as a placeholderAdd --allow-remote
OVERFLOW — 1 slide exceed the frameThe slide holds more than fits. Render with --overflow-marker author to see it, then cut or split the content
  • CLI reference — every flag, variable and exit code.
  • Deck settings — the front-matter keys, including player:, read:, fluid: and present:, which do the same job as the flags on this page.
  • Themes & palettes — the palettes you can name with -p.