A Markdown slide-deck engine for boardroom-quality decks — PDF, HTML, PPTX, or PNG sets.
Lattice produces decks where every slide is a deliberate component (title, diagram, verdict-grid, and dozens more), all themed through a single CSS palette and rendered to your delivery format with no manual formatting work. Decks read as ink-on-paper and pass WCAG AA throughout.
You author every slide as plain Markdown — bullet lists, tables, fenced
code, $…$ math — and Lattice renders it in the visual vocabulary your
field expects. Mathematicians and quants get KaTeX with
Definition/Theorem/Proof cards, derivation chains, and matrix
decompositions; project leads get gantt charts, kanban boards, and
roadmaps; engineers and architects get all 25 Mermaid diagram types and
side-by-side code diffs; lawyers and compliance get statute stacks,
authority chains, and obligation matrices; analysts get radar, quadrant,
and KPI components. More than fifty components, one syntax you already
know — no drawing tools, no boxes, no pasted screenshots.
Lattice is the engine at the heart of Laticent — the project born from a refusal to keep fighting the slide tools we all use, one that builds deck-quality documents with the discipline of a design system. (Read the story.) Laticent is also the name of the desktop app (under development), which runs this same engine, so a deck authored from the command line or in the app renders identically.
I built Lattice because the slide tools we all use make authors unproductive by design — a blank canvas, no guardrails, and no way to see what changed between two versions. Lattice is the fix I wanted: a deck as a text file, where the design system's discipline and taste live in the engine instead of being left to chance. The full story — and what the name means — is at lattice.style/story.
Documentation: https://laticent.github.io/lattice/ — intro, getting started, authoring and theming guides, and the interactive component reference (every component, themable in any palette). Built from
docs/; seedocs/README.md.
lattice-emulator.js emits PDF, PPTX, and PNG
sets (plus an HTML sidecar) from the same source — the output extension picks
the format, and PPTX/PNG rasterize from the same render as the PDF. Mermaid diagrams pre-render
as inline SVG. Code blocks syntax-highlight. Slides are 1280×720.indaco (cool indigo, the default) and cuoio
(warm leather) are the canonical pair, alongside ardesia, atelier,
brina, burgundy, carbone, carta, concrete, crepuscolo,
laguna, magnolia, mustard, and onyx — most with a paired dark-canvas
variant. Authors pick one in front matter (theme: indaco). Each
supplies pale fills, saturated brand borders, and dark ink; saturated
red is reserved for alarm states. WCAG AA verified across every
text-bearing surface. Four additional color-blind-safe palettes
(a11y-deuteranopia, a11y-protanopia, a11y-tritanopia,
a11y-achromatopsia) re-tune the categorical hues for the common
color-vision deficiencies and are selectable in the picker. Preview them all in the
component reference.themeVariables. Documented in design/theming.md.git clone https://github.com/Laticent/lattice.git
cd lattice
npm install
npm install also builds dist/ — the engine bundles, the CLI and the
docs-site bundles are generated, not committed (they conflicted in the merge
queue on every other PR). It takes ~16s and needs no browser. If you ever need to
regenerate them by hand, npm run build.
Requires Node 22.12+ (require() of an ES module, which lib/authoring uses to
share the class-directive scanner, is unflagged from that release).
npm install pulls in the Mermaid CLI and Puppeteer
(which downloads a matching Chromium). It does not pull Marp — the owned
engine renders every first-party path.
Distributed as @laticent/lattice (npm publishing is pending — see
RELEASE.md). The bundled lattice bin renders through the owned
engine — there is no Marp dependency or Marp render path:
npm install @laticent/lattice
# The emulator, exposed as a bin (the owned engine). Resolves the engine +
# every theme relative to the installed package, so it works from any dir.
npx lattice deck.md deck.pdf
Need a portable bundle for someone who renders with Marp? The docs-site
Studio's Export to Marp (also available via npm run export:marp)
produces a self-contained .zip. That bundle
is the only Marp interop surface; Lattice itself never renders through Marp.
The package also exposes these named entry points:
| Subpath | Resolves to | For |
|---|---|---|
@laticent/lattice/default | dist/lattice-default.css | zero-config default — engine + the cuoio palette, flattened into one drop-in stylesheet |
@laticent/lattice/default/min | dist/lattice-default.min.css | minified zero-config default — the leanest single-file <link> for browser use |
@laticent/lattice/engine | lib/engine/index.js | the canonical render kernel (render() + the transform pipeline) — for embedding the engine directly (HARD RULE #1: this is the source of truth all render paths share) |
@laticent/lattice/runtime | dist/lattice-runtime.js | the preview / web-export runtime transforms |
@laticent/lattice/runtime/min | dist/lattice-runtime.min.js | minified runtime — production / CDN drop-in (no inline source map). Not self-sufficient: ship dist/lattice-dagre.min.js beside it (see Embed in a browser) |
| — | dist/lattice-dagre.min.js | the graph-layout engine for a state chart that BRANCHES. Load it before the runtime. Its absence is silent on the slide — the chart draws as a numbered column |
@laticent/lattice/css | dist/lattice.css | the engine bundle — palette-blind (components only, no color tokens) |
@laticent/lattice/css/min | dist/lattice.min.css | minified engine bundle (Marp @theme/@size directives preserved) |
@laticent/lattice/themes/<name>.css | themes/<name>.css | one palette — a Marp theme file, not a standalone stylesheet |
lattice bin · @laticent/lattice (main/.) | dist/lattice-emulator.js | the bundled CLI renderer / PDF exporter (npx lattice deck.md out.pdf) |
@laticent/lattice/min | dist/lattice-emulator.min.js | minified CLI bundle (shebang + executable bit preserved); the bin/main stays the unminified file |
The .min variants are byte-for-byte render-faithful to their unminified
siblings — the CSS minifier preserves Marp's directive comments, so a
minified bundle still registers as a theme. Pick the unminified files for
debugging (the CLI/runtime carry source maps; the CSS keeps comments) and
the .min files for production / CDN delivery.
The default theme is cuoio (warm leather/cream). In a Marp deck,
theme: cuoio selects it; with no theme chosen, decks render against the
engine's neutral built-in tokens. For a non-Marp / browser context, drop
in the flattened default — a single self-contained stylesheet:
<link rel="stylesheet" href="…/@laticent/lattice/default"> <!-- engine + cuoio -->
Per-theme files are Marp theme files, not drop-in CSS. Each declares
@theme <name>and pulls the engine in by name (@import 'lattice'), which only Marp's theme set resolves — a browser<link>to a theme file can't resolve it, anddist/lattice.cssalone is palette-blind. The flatteneddist/lattice-default.cssis the exception: its@importis resolved at build time, so it is genuinely browser-droppable. Only cuoio is flattened today; other palettes would each be a new flatten target.
The published tarball ships only what these entry points need — engine
source, dist/, themes/, and the authoring docs. Regression-baseline
PDFs and per-bucket galleries stay in git but are excluded from the
package.
The bundled lattice bin (the emulator) emits all of them from one source —
the output extension picks the format — plus an HTML sidecar:
lattice deck.md deck.pdf # vector PDF, selectable text
lattice deck.md deck.pptx # PowerPoint, one full-bleed image per slide
lattice deck.md deck.png # one PNG per slide → deck.001.png, deck.002.png, …
PPTX and PNG rasterize from the same headless-Chromium render as the PDF, so
every format is pixel-identical. Rendering needs Chromium — set CHROME_PATH
if no system Chrome is found.
The recognized extensions are .pdf, .pptx, .png, .zip (an image set) and
.html; anything else is a usage error rather than a silent PDF under the wrong
name. (An output path with no extension at all still renders the PDF — nothing is
mislabeled when nothing is labeled.) For per-slide JPEG or WebP, render a .zip
with --image-format jpeg|webp.
PPTX note. Slides export as one full-bleed image per slide (not editable text/shapes). Editable PPTX export (which needs LibreOffice) is not included.
Pass --present to mark the PDF to open straight into full-screen
presentation mode with a subtle cross-fade between slides:
lattice deck.md deck.pdf --present
Adobe Acrobat/Reader and most desktop viewers honor this (it's the same
document hint Keynote and PowerPoint emit); browser-embedded viewers and macOS
Preview ignore it harmlessly. Slides stay presenter-driven — no auto-advance.
A deck can also bake this in with a present: true front-matter key.
Pass --fluid (or fluid: true) to emit a responsive, phone-readable HTML
viewer instead of a fixed-box export — slides reflow to the viewport and you
swipe between them, so the same deck reads on a phone without pinch-zooming:
lattice deck.md deck.html --fluid
Three more PDF delivery options:
lattice deck.md deck.pdf --raster # one full-page image per slide
lattice deck.md deck.pdf --embed-source # attach the .md inside the PDF
lattice deck.md deck.pdf --keep-vector-images # keep SVG images as vectors
--raster trades selectable text for maximum viewer compatibility — every page
is a single full-bleed 2× JPEG (speaker notes, --present, and
--embed-source still apply). --embed-source attaches the deck's Markdown
source as a PDF embedded file, so anyone holding the PDF can recover and
re-render the deck (note: that ships your source, speaker notes included, inside
the artifact). By default the exporter also rasterizes SVG images
(, inline ) into the otherwise-vector PDF,
because iOS's built-in PDF viewer mishandles the vector constructs Chromium
prints for them; --keep-vector-images opts back into vectors. Inline SVG —
Mermaid diagrams, charts, logo marks — always stays vector.
npx lattice test/integration/baseline-decks/gallery.md gallery.pdf
The gallery is committed to test/integration/baseline-decks/ as the
ground-truth fixture for what the renderer produces (the docs site serves
the rendered PDF at
/gallery.pdf).
Re-rendering it after an engine or palette change should produce visually
equivalent output; it's the regression check for the project.
For other delivery formats from the same source, just change the output
extension — the deck's theme: front matter selects the palette:
node lattice-emulator.js deck.md deck.pptx # PowerPoint (image slides)
node lattice-emulator.js deck.md deck.png # → deck.001.png, deck.002.png, …
PNG slides rasterize at 2× the slide dimensions (2560×1440 from 1280×720) — sharp on retina displays and projectors. PDF is vector throughout (text, SVG-rendered Mermaid, code highlighting); the 2× scale only affects the raster (PNG/PPTX) paths.
The full pipeline (Mermaid pre-rendering, image conversion, PPTX assembly) lives in engineering/pipeline.md.
Set theme: in your deck's front matter:
---
theme: indaco # cool indigo (default)
---
or
---
theme: cuoio # warm leather
---
For CLI builds, the active palette can also be overridden positionally:
node lattice-emulator.js deck.md out.pdf <palette-name>
The third positional argument names a file in themes/. The default is
indaco. To author a new palette, copy themes/indaco.css, change its
@theme directive to your name, and edit the tokens. See
design/theming.md for the variable contract and the per-diagram
Mermaid override surface.
For web-export contexts, include dist/lattice-runtime.js and the two engines it
expects to find beside it:
<link rel="stylesheet" href="themes/indaco.css">
<link rel="stylesheet" href="dist/lattice.css">
<script src="mermaid-v11.min.js"></script>
<script src="dist/lattice-dagre.min.js"></script>
<script src="dist/lattice-runtime.js"></script>
Order matters, and so does the third tag. Mermaid and dagre install globals the
runtime reads on its first pass, and classic scripts run in document order — a tag
placed after the runtime arrives too late. Omitting lattice-dagre.min.js is the
quiet one: a state chart still draws, but one that BRANCHES falls back to a
numbered column, which looks like a deliberate layout rather than a missing asset.
The runtime says so on the console. Decks exported by Lattice (--format marp, the
marp kit) carry all three automatically.
Both engines are vendored, not fetched: this snippet used to point Mermaid at
jsdelivr, which is the CDN dependency
engineering/decisions/2026-09-03-self-hosted-runtime-deps.md removed from the
codebase — mermaid-v11.min.js ships in the repo root and in every export bundle.
Keep dist/fonts/ beside dist/lattice.css — the @font-face srcs are
stylesheet-relative, so moving the CSS without the directory drops the deck to
system serif/sans on every slide.
The runtime reads CSS custom properties from the loaded palette, derives
the Mermaid themeVariables object, and fetches the Mermaid CSS section
from the palette file. Same theme as the build path; one file to edit.
It also composes every slide as Form by default — the masthead band, bay,
progress rail, and section watermarks — matching the engine, so a deck dropped
into a Marp tool gets the full layout with no per-slide tagging. Opt a single
slide out with a no-form class (DOM-visible, honored everywhere). The
deck-wide form: off opt-out is applied only on Lattice's own engine render
paths (the lattice CLI, the docs playground); a Marp-rendered surface never
runs the toggle, and the runtime reads no front matter, so it composes Form
there regardless. See design/forms.md and
engineering/decisions/2026-07-08-runtime-form-default.md.
Where this actually runs. Anywhere the browser executes the <script>
tags: a plain HTML page, an Export-to-Marp bundle's .html, and marp-cli's
marp --html … --pdf (it drives a real headless browser). Marp tools need
raw HTML enabled or the tags are escaped into visible text — html: true /
--html for marp-cli, markdown.marp.enableHtml for the VS Code extension.
The one surface where this is not guaranteed is the marp-vscode preview
pane — its webview is understood to block script execution, which would leave
runtime-composed structure (Form, split panels, Mermaid, the chart family) flat
there. That reading is unverified and disputed by at least one field report, so
treat the preview as best-effort: use lattice render or the exported HTML/PDF
when you need to trust what you see. See engineering/gotchas.md
§ "VS Code / marp-vscode".
A count-free orientation — exact file lists and component/theme/gallery counts drift, so they're not duplicated here. The canonical maps are generated or indexed elsewhere; follow the pointers.
lattice/
├── lattice-emulator.js # the owned engine (CLI renderer; esbuild entry)
├── dist/ # GENERATED, NOT committed — built by `npm install` / `npm run build`
│ # (lattice.css, the runtime/emulator bundles, docs/).
│ # Regenerate: npm run build · index: dist/README.md
├── lib/ # engine source: core kernels, transformers, components/, theme/, forms/
├── themes/ # palettes (each light theme + a paired dark variant)
├── examples/ # the galleries + per-feature demo decks (+ committed PDFs)
├── docs/ # the public documentation site (separate Astro package)
├── engineering/ # internal engineering references (+ decisions/ — design/ADR notes)
├── design/ # the design system + the deck-authoring contract
├── test/ # unit/ (fast, no child procs) + integration/ (renders)
└── tools/ # build, lint, render, and audit scripts
CLAUDE.md.engineering/capabilities.md
(generated from package.json + tools/).dist/docs/components.pick.md (pick a
component), dist/docs/components.json (the full machine record) and
design/design-system.md. dist/ is generated —
npm install builds it; without a clone, the same catalogs plus the authoring
primer are published on the dist-kits branch.Two tiers, both built on Node's node:test:
npm test # unit tier — palette, var-map contract, source parse
npm run test:integration # integration tier — rebuilds the galleries through
# lattice-emulator and the runtime; cross-renderer parity
npm run test:all # both tiers
The unit tier finishes in under 100 ms and is the inner loop. The
integration tier takes ~30 s and is what CI runs before merge. The
top-level gallery (test/integration/baseline-decks/gallery.md) is the
authoritative test fixture; its committed PDF is the regression
baseline. Expected page counts are inlined in each test file; the 58
per-component galleries derive their counts from the manifest itself
via expectedGallerySlideCount().
The integration suite asserts cross-renderer parity between the two
render paths — the owned engine (lattice-emulator.js) and the browser
runtime (dist/lattice-runtime.js).
Lattice is the engine at the core of Laticent, a project that publishes tools for crafting deck-quality documents. The repositories (current and planned) live at github.com/Laticent:
node
from a terminal. (Under development.)Lattice follows semantic versioning with one explicit contract: components and palette tokens are stable. A breaking change to either is a major version bump. New components and new palettes are additive minor versions. Mermaid CSS overrides are internal and may change in patch versions. See CHANGELOG.md for the per-version detail.
GNU Affero General Public License v3.0 (AGPL-3.0-only). See LICENSE.
Copyright © 2025–2026 Sharmarke Aden, doing business as Laticent.
Lattice is free and open source, and everyday use carries no copyleft obligations: rendering decks on your laptop or in CI is not distribution, and every deck you render is yours outright — PDFs and PPTX are uncovered output, and the Lattice Output Exception waives the AGPL for the engine CSS/JS that exported HTML decks embed, so publishing a deck on a web server imposes nothing on you. The AGPL's conditions attach to the engine itself — if you redistribute it, serve it to browsers (shipping the JS/CSS bundles in a web app counts), or run a modified version as a network service, your engine source must be available under the same license. To embed Lattice in a closed-source product under different terms, contact Laticent about a commercial license: sharmarke.aden@gmail.com.
The "Lattice" and "Laticent" names and logos are trademarks — the license does not grant them; forks must rename. See TRADEMARKS.md.
JavaScript
84.3%
CSS
14.0%
HTML
1.1%
A Markdown slide-deck engine for boardroom-quality decks — PDF, HTML, PPTX, or PNG sets.
Lattice produces decks where every slide is a deliberate component (title, diagram, verdict-grid, and dozens more), all themed through a single CSS palette and rendered to your delivery format with no manual formatting work. Decks read as ink-on-paper and pass WCAG AA throughout.
You author every slide as plain Markdown — bullet lists, tables, fenced
code, $…$ math — and Lattice renders it in the visual vocabulary your
field expects. Mathematicians and quants get KaTeX with
Definition/Theorem/Proof cards, derivation chains, and matrix
decompositions; project leads get gantt charts, kanban boards, and
roadmaps; engineers and architects get all 25 Mermaid diagram types and
side-by-side code diffs; lawyers and compliance get statute stacks,
authority chains, and obligation matrices; analysts get radar, quadrant,
and KPI components. More than fifty components, one syntax you already
know — no drawing tools, no boxes, no pasted screenshots.
Lattice is the engine at the heart of Laticent — the project born from a refusal to keep fighting the slide tools we all use, one that builds deck-quality documents with the discipline of a design system. (Read the story.) Laticent is also the name of the desktop app (under development), which runs this same engine, so a deck authored from the command line or in the app renders identically.
I built Lattice because the slide tools we all use make authors unproductive by design — a blank canvas, no guardrails, and no way to see what changed between two versions. Lattice is the fix I wanted: a deck as a text file, where the design system's discipline and taste live in the engine instead of being left to chance. The full story — and what the name means — is at lattice.style/story.
Documentation: https://laticent.github.io/lattice/ — intro, getting started, authoring and theming guides, and the interactive component reference (every component, themable in any palette). Built from
docs/; seedocs/README.md.
lattice-emulator.js emits PDF, PPTX, and PNG
sets (plus an HTML sidecar) from the same source — the output extension picks
the format, and PPTX/PNG rasterize from the same render as the PDF. Mermaid diagrams pre-render
as inline SVG. Code blocks syntax-highlight. Slides are 1280×720.indaco (cool indigo, the default) and cuoio
(warm leather) are the canonical pair, alongside ardesia, atelier,
brina, burgundy, carbone, carta, concrete, crepuscolo,
laguna, magnolia, mustard, and onyx — most with a paired dark-canvas
variant. Authors pick one in front matter (theme: indaco). Each
supplies pale fills, saturated brand borders, and dark ink; saturated
red is reserved for alarm states. WCAG AA verified across every
text-bearing surface. Four additional color-blind-safe palettes
(a11y-deuteranopia, a11y-protanopia, a11y-tritanopia,
a11y-achromatopsia) re-tune the categorical hues for the common
color-vision deficiencies and are selectable in the picker. Preview them all in the
component reference.themeVariables. Documented in design/theming.md.git clone https://github.com/Laticent/lattice.git
cd lattice
npm install
npm install also builds dist/ — the engine bundles, the CLI and the
docs-site bundles are generated, not committed (they conflicted in the merge
queue on every other PR). It takes ~16s and needs no browser. If you ever need to
regenerate them by hand, npm run build.
Requires Node 22.12+ (require() of an ES module, which lib/authoring uses to
share the class-directive scanner, is unflagged from that release).
npm install pulls in the Mermaid CLI and Puppeteer
(which downloads a matching Chromium). It does not pull Marp — the owned
engine renders every first-party path.
Distributed as @laticent/lattice (npm publishing is pending — see
RELEASE.md). The bundled lattice bin renders through the owned
engine — there is no Marp dependency or Marp render path:
npm install @laticent/lattice
# The emulator, exposed as a bin (the owned engine). Resolves the engine +
# every theme relative to the installed package, so it works from any dir.
npx lattice deck.md deck.pdf
Need a portable bundle for someone who renders with Marp? The docs-site
Studio's Export to Marp (also available via npm run export:marp)
produces a self-contained .zip. That bundle
is the only Marp interop surface; Lattice itself never renders through Marp.
The package also exposes these named entry points:
| Subpath | Resolves to | For |
|---|---|---|
@laticent/lattice/default | dist/lattice-default.css | zero-config default — engine + the cuoio palette, flattened into one drop-in stylesheet |
@laticent/lattice/default/min | dist/lattice-default.min.css | minified zero-config default — the leanest single-file <link> for browser use |
@laticent/lattice/engine | lib/engine/index.js | the canonical render kernel (render() + the transform pipeline) — for embedding the engine directly (HARD RULE #1: this is the source of truth all render paths share) |
@laticent/lattice/runtime | dist/lattice-runtime.js | the preview / web-export runtime transforms |
@laticent/lattice/runtime/min | dist/lattice-runtime.min.js | minified runtime — production / CDN drop-in (no inline source map). Not self-sufficient: ship dist/lattice-dagre.min.js beside it (see Embed in a browser) |
| — | dist/lattice-dagre.min.js | the graph-layout engine for a state chart that BRANCHES. Load it before the runtime. Its absence is silent on the slide — the chart draws as a numbered column |
@laticent/lattice/css | dist/lattice.css | the engine bundle — palette-blind (components only, no color tokens) |
@laticent/lattice/css/min | dist/lattice.min.css | minified engine bundle (Marp @theme/@size directives preserved) |
@laticent/lattice/themes/<name>.css | themes/<name>.css | one palette — a Marp theme file, not a standalone stylesheet |
lattice bin · @laticent/lattice (main/.) | dist/lattice-emulator.js | the bundled CLI renderer / PDF exporter (npx lattice deck.md out.pdf) |
@laticent/lattice/min | dist/lattice-emulator.min.js | minified CLI bundle (shebang + executable bit preserved); the bin/main stays the unminified file |
The .min variants are byte-for-byte render-faithful to their unminified
siblings — the CSS minifier preserves Marp's directive comments, so a
minified bundle still registers as a theme. Pick the unminified files for
debugging (the CLI/runtime carry source maps; the CSS keeps comments) and
the .min files for production / CDN delivery.
The default theme is cuoio (warm leather/cream). In a Marp deck,
theme: cuoio selects it; with no theme chosen, decks render against the
engine's neutral built-in tokens. For a non-Marp / browser context, drop
in the flattened default — a single self-contained stylesheet:
<link rel="stylesheet" href="…/@laticent/lattice/default"> <!-- engine + cuoio -->
Per-theme files are Marp theme files, not drop-in CSS. Each declares
@theme <name>and pulls the engine in by name (@import 'lattice'), which only Marp's theme set resolves — a browser<link>to a theme file can't resolve it, anddist/lattice.cssalone is palette-blind. The flatteneddist/lattice-default.cssis the exception: its@importis resolved at build time, so it is genuinely browser-droppable. Only cuoio is flattened today; other palettes would each be a new flatten target.
The published tarball ships only what these entry points need — engine
source, dist/, themes/, and the authoring docs. Regression-baseline
PDFs and per-bucket galleries stay in git but are excluded from the
package.
The bundled lattice bin (the emulator) emits all of them from one source —
the output extension picks the format — plus an HTML sidecar:
lattice deck.md deck.pdf # vector PDF, selectable text
lattice deck.md deck.pptx # PowerPoint, one full-bleed image per slide
lattice deck.md deck.png # one PNG per slide → deck.001.png, deck.002.png, …
PPTX and PNG rasterize from the same headless-Chromium render as the PDF, so
every format is pixel-identical. Rendering needs Chromium — set CHROME_PATH
if no system Chrome is found.
The recognized extensions are .pdf, .pptx, .png, .zip (an image set) and
.html; anything else is a usage error rather than a silent PDF under the wrong
name. (An output path with no extension at all still renders the PDF — nothing is
mislabeled when nothing is labeled.) For per-slide JPEG or WebP, render a .zip
with --image-format jpeg|webp.
PPTX note. Slides export as one full-bleed image per slide (not editable text/shapes). Editable PPTX export (which needs LibreOffice) is not included.
Pass --present to mark the PDF to open straight into full-screen
presentation mode with a subtle cross-fade between slides:
lattice deck.md deck.pdf --present
Adobe Acrobat/Reader and most desktop viewers honor this (it's the same
document hint Keynote and PowerPoint emit); browser-embedded viewers and macOS
Preview ignore it harmlessly. Slides stay presenter-driven — no auto-advance.
A deck can also bake this in with a present: true front-matter key.
Pass --fluid (or fluid: true) to emit a responsive, phone-readable HTML
viewer instead of a fixed-box export — slides reflow to the viewport and you
swipe between them, so the same deck reads on a phone without pinch-zooming:
lattice deck.md deck.html --fluid
Three more PDF delivery options:
lattice deck.md deck.pdf --raster # one full-page image per slide
lattice deck.md deck.pdf --embed-source # attach the .md inside the PDF
lattice deck.md deck.pdf --keep-vector-images # keep SVG images as vectors
--raster trades selectable text for maximum viewer compatibility — every page
is a single full-bleed 2× JPEG (speaker notes, --present, and
--embed-source still apply). --embed-source attaches the deck's Markdown
source as a PDF embedded file, so anyone holding the PDF can recover and
re-render the deck (note: that ships your source, speaker notes included, inside
the artifact). By default the exporter also rasterizes SVG images
(, inline ) into the otherwise-vector PDF,
because iOS's built-in PDF viewer mishandles the vector constructs Chromium
prints for them; --keep-vector-images opts back into vectors. Inline SVG —
Mermaid diagrams, charts, logo marks — always stays vector.
npx lattice test/integration/baseline-decks/gallery.md gallery.pdf
The gallery is committed to test/integration/baseline-decks/ as the
ground-truth fixture for what the renderer produces (the docs site serves
the rendered PDF at
/gallery.pdf).
Re-rendering it after an engine or palette change should produce visually
equivalent output; it's the regression check for the project.
For other delivery formats from the same source, just change the output
extension — the deck's theme: front matter selects the palette:
node lattice-emulator.js deck.md deck.pptx # PowerPoint (image slides)
node lattice-emulator.js deck.md deck.png # → deck.001.png, deck.002.png, …
PNG slides rasterize at 2× the slide dimensions (2560×1440 from 1280×720) — sharp on retina displays and projectors. PDF is vector throughout (text, SVG-rendered Mermaid, code highlighting); the 2× scale only affects the raster (PNG/PPTX) paths.
The full pipeline (Mermaid pre-rendering, image conversion, PPTX assembly) lives in engineering/pipeline.md.
Set theme: in your deck's front matter:
---
theme: indaco # cool indigo (default)
---
or
---
theme: cuoio # warm leather
---
For CLI builds, the active palette can also be overridden positionally:
node lattice-emulator.js deck.md out.pdf <palette-name>
The third positional argument names a file in themes/. The default is
indaco. To author a new palette, copy themes/indaco.css, change its
@theme directive to your name, and edit the tokens. See
design/theming.md for the variable contract and the per-diagram
Mermaid override surface.
For web-export contexts, include dist/lattice-runtime.js and the two engines it
expects to find beside it:
<link rel="stylesheet" href="themes/indaco.css">
<link rel="stylesheet" href="dist/lattice.css">
<script src="mermaid-v11.min.js"></script>
<script src="dist/lattice-dagre.min.js"></script>
<script src="dist/lattice-runtime.js"></script>
Order matters, and so does the third tag. Mermaid and dagre install globals the
runtime reads on its first pass, and classic scripts run in document order — a tag
placed after the runtime arrives too late. Omitting lattice-dagre.min.js is the
quiet one: a state chart still draws, but one that BRANCHES falls back to a
numbered column, which looks like a deliberate layout rather than a missing asset.
The runtime says so on the console. Decks exported by Lattice (--format marp, the
marp kit) carry all three automatically.
Both engines are vendored, not fetched: this snippet used to point Mermaid at
jsdelivr, which is the CDN dependency
engineering/decisions/2026-09-03-self-hosted-runtime-deps.md removed from the
codebase — mermaid-v11.min.js ships in the repo root and in every export bundle.
Keep dist/fonts/ beside dist/lattice.css — the @font-face srcs are
stylesheet-relative, so moving the CSS without the directory drops the deck to
system serif/sans on every slide.
The runtime reads CSS custom properties from the loaded palette, derives
the Mermaid themeVariables object, and fetches the Mermaid CSS section
from the palette file. Same theme as the build path; one file to edit.
It also composes every slide as Form by default — the masthead band, bay,
progress rail, and section watermarks — matching the engine, so a deck dropped
into a Marp tool gets the full layout with no per-slide tagging. Opt a single
slide out with a no-form class (DOM-visible, honored everywhere). The
deck-wide form: off opt-out is applied only on Lattice's own engine render
paths (the lattice CLI, the docs playground); a Marp-rendered surface never
runs the toggle, and the runtime reads no front matter, so it composes Form
there regardless. See design/forms.md and
engineering/decisions/2026-07-08-runtime-form-default.md.
Where this actually runs. Anywhere the browser executes the <script>
tags: a plain HTML page, an Export-to-Marp bundle's .html, and marp-cli's
marp --html … --pdf (it drives a real headless browser). Marp tools need
raw HTML enabled or the tags are escaped into visible text — html: true /
--html for marp-cli, markdown.marp.enableHtml for the VS Code extension.
The one surface where this is not guaranteed is the marp-vscode preview
pane — its webview is understood to block script execution, which would leave
runtime-composed structure (Form, split panels, Mermaid, the chart family) flat
there. That reading is unverified and disputed by at least one field report, so
treat the preview as best-effort: use lattice render or the exported HTML/PDF
when you need to trust what you see. See engineering/gotchas.md
§ "VS Code / marp-vscode".
A count-free orientation — exact file lists and component/theme/gallery counts drift, so they're not duplicated here. The canonical maps are generated or indexed elsewhere; follow the pointers.
lattice/
├── lattice-emulator.js # the owned engine (CLI renderer; esbuild entry)
├── dist/ # GENERATED, NOT committed — built by `npm install` / `npm run build`
│ # (lattice.css, the runtime/emulator bundles, docs/).
│ # Regenerate: npm run build · index: dist/README.md
├── lib/ # engine source: core kernels, transformers, components/, theme/, forms/
├── themes/ # palettes (each light theme + a paired dark variant)
├── examples/ # the galleries + per-feature demo decks (+ committed PDFs)
├── docs/ # the public documentation site (separate Astro package)
├── engineering/ # internal engineering references (+ decisions/ — design/ADR notes)
├── design/ # the design system + the deck-authoring contract
├── test/ # unit/ (fast, no child procs) + integration/ (renders)
└── tools/ # build, lint, render, and audit scripts
CLAUDE.md.engineering/capabilities.md
(generated from package.json + tools/).dist/docs/components.pick.md (pick a
component), dist/docs/components.json (the full machine record) and
design/design-system.md. dist/ is generated —
npm install builds it; without a clone, the same catalogs plus the authoring
primer are published on the dist-kits branch.Two tiers, both built on Node's node:test:
npm test # unit tier — palette, var-map contract, source parse
npm run test:integration # integration tier — rebuilds the galleries through
# lattice-emulator and the runtime; cross-renderer parity
npm run test:all # both tiers
The unit tier finishes in under 100 ms and is the inner loop. The
integration tier takes ~30 s and is what CI runs before merge. The
top-level gallery (test/integration/baseline-decks/gallery.md) is the
authoritative test fixture; its committed PDF is the regression
baseline. Expected page counts are inlined in each test file; the 58
per-component galleries derive their counts from the manifest itself
via expectedGallerySlideCount().
The integration suite asserts cross-renderer parity between the two
render paths — the owned engine (lattice-emulator.js) and the browser
runtime (dist/lattice-runtime.js).
Lattice is the engine at the core of Laticent, a project that publishes tools for crafting deck-quality documents. The repositories (current and planned) live at github.com/Laticent:
node
from a terminal. (Under development.)Lattice follows semantic versioning with one explicit contract: components and palette tokens are stable. A breaking change to either is a major version bump. New components and new palettes are additive minor versions. Mermaid CSS overrides are internal and may change in patch versions. See CHANGELOG.md for the per-version detail.
GNU Affero General Public License v3.0 (AGPL-3.0-only). See LICENSE.
Copyright © 2025–2026 Sharmarke Aden, doing business as Laticent.
Lattice is free and open source, and everyday use carries no copyleft obligations: rendering decks on your laptop or in CI is not distribution, and every deck you render is yours outright — PDFs and PPTX are uncovered output, and the Lattice Output Exception waives the AGPL for the engine CSS/JS that exported HTML decks embed, so publishing a deck on a web server imposes nothing on you. The AGPL's conditions attach to the engine itself — if you redistribute it, serve it to browsers (shipping the JS/CSS bundles in a web app counts), or run a modified version as a network service, your engine source must be available under the same license. To embed Lattice in a closed-source product under different terms, contact Laticent about a commercial license: sharmarke.aden@gmail.com.
The "Lattice" and "Laticent" names and logos are trademarks — the license does not grant them; forks must rename. See TRADEMARKS.md.
JavaScript
84.3%
CSS
14.0%
HTML
1.1%