Laticent/lattice

Markdown for the boardroom: write plain text, pick from 53 themable components, render a publish-ready PDF and PPTX — no dragging, no drift, all style and substance. This is the Lattice engine.

2

stars

2,156

commits

JavaScript

primary language

Sep 9, 2026

updated

README

Lattice

Lattice

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.

Why Laticent exists

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/; see docs/README.md.

What you get

  • A renderer. The bundled 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.
  • Fourteen palettes. 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.
  • More than fifty components. Title, divider, content, diagram, cards-grid, compare-prose, quote, timeline-list, big-number, split-panel, verdict-grid, more. Each component has an authoring contract documented in design/skill.md.
  • Mermaid integration. All 25 renderable Mermaid diagram types are themed to match the deck. Per-diagram CSS overrides for the nine that ignore themeVariables. Documented in design/theming.md.

Install

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.

Use as a package

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:

SubpathResolves toFor
@laticent/lattice/defaultdist/lattice-default.csszero-config default — engine + the cuoio palette, flattened into one drop-in stylesheet
@laticent/lattice/default/mindist/lattice-default.min.cssminified zero-config default — the leanest single-file <link> for browser use
@laticent/lattice/enginelib/engine/index.jsthe 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/runtimedist/lattice-runtime.jsthe preview / web-export runtime transforms
@laticent/lattice/runtime/mindist/lattice-runtime.min.jsminified 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.jsthe 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/cssdist/lattice.cssthe engine bundle — palette-blind (components only, no color tokens)
@laticent/lattice/css/mindist/lattice.min.cssminified engine bundle (Marp @theme/@size directives preserved)
@laticent/lattice/themes/<name>.cssthemes/<name>.cssone palette — a Marp theme file, not a standalone stylesheet
lattice bin · @laticent/lattice (main/.)dist/lattice-emulator.jsthe bundled CLI renderer / PDF exporter (npx lattice deck.md out.pdf)
@laticent/lattice/mindist/lattice-emulator.min.jsminified 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, and dist/lattice.css alone is palette-blind. The flattened dist/lattice-default.css is the exception: its @import is 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.

Render to PDF, PPTX, or PNG

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 (![bg](photo.svg), inline ![](photo.svg)) 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.

Use a different palette

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.

Embed in a browser

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".

Project layout

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
  • Which doc covers what → the "Read the canonical doc before working in its area" table in CLAUDE.md.
  • Every script/tool that existsengineering/capabilities.md (generated from package.json + tools/).
  • The component/theme/token catalogdist/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.

Testing

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).

The Laticent project

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:

  • lattice — this repo. The deck rendering engine + default palette.
  • Laticent — the desktop app (Tauri). Wraps the Lattice engine with a markdown editor, live preview, theme picker, and PDF export. Aims to make Lattice approachable for people who don't run node from a terminal. (Under development.)
  • themes — additional palette packs that can drop into Lattice. (Future.)

Versioning

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.

License

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.

Contributors

saden1

1,584 commits

claude

475 commits

dependabot[bot]

10 commits

Laticent/lattice

Markdown for the boardroom: write plain text, pick from 53 themable components, render a publish-ready PDF and PPTX — no dragging, no drift, all style and substance. This is the Lattice engine.

2

stars

2,156

commits

JavaScript

primary language

Sep 9, 2026

updated

README

Lattice

Lattice

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.

Why Laticent exists

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/; see docs/README.md.

What you get

  • A renderer. The bundled 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.
  • Fourteen palettes. 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.
  • More than fifty components. Title, divider, content, diagram, cards-grid, compare-prose, quote, timeline-list, big-number, split-panel, verdict-grid, more. Each component has an authoring contract documented in design/skill.md.
  • Mermaid integration. All 25 renderable Mermaid diagram types are themed to match the deck. Per-diagram CSS overrides for the nine that ignore themeVariables. Documented in design/theming.md.

Install

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.

Use as a package

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:

SubpathResolves toFor
@laticent/lattice/defaultdist/lattice-default.csszero-config default — engine + the cuoio palette, flattened into one drop-in stylesheet
@laticent/lattice/default/mindist/lattice-default.min.cssminified zero-config default — the leanest single-file <link> for browser use
@laticent/lattice/enginelib/engine/index.jsthe 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/runtimedist/lattice-runtime.jsthe preview / web-export runtime transforms
@laticent/lattice/runtime/mindist/lattice-runtime.min.jsminified 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.jsthe 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/cssdist/lattice.cssthe engine bundle — palette-blind (components only, no color tokens)
@laticent/lattice/css/mindist/lattice.min.cssminified engine bundle (Marp @theme/@size directives preserved)
@laticent/lattice/themes/<name>.cssthemes/<name>.cssone palette — a Marp theme file, not a standalone stylesheet
lattice bin · @laticent/lattice (main/.)dist/lattice-emulator.jsthe bundled CLI renderer / PDF exporter (npx lattice deck.md out.pdf)
@laticent/lattice/mindist/lattice-emulator.min.jsminified 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, and dist/lattice.css alone is palette-blind. The flattened dist/lattice-default.css is the exception: its @import is 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.

Render to PDF, PPTX, or PNG

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 (![bg](photo.svg), inline ![](photo.svg)) 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.

Use a different palette

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.

Embed in a browser

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".

Project layout

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
  • Which doc covers what → the "Read the canonical doc before working in its area" table in CLAUDE.md.
  • Every script/tool that existsengineering/capabilities.md (generated from package.json + tools/).
  • The component/theme/token catalogdist/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.

Testing

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).

The Laticent project

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:

  • lattice — this repo. The deck rendering engine + default palette.
  • Laticent — the desktop app (Tauri). Wraps the Lattice engine with a markdown editor, live preview, theme picker, and PDF export. Aims to make Lattice approachable for people who don't run node from a terminal. (Under development.)
  • themes — additional palette packs that can drop into Lattice. (Future.)

Versioning

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.

License

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.

Contributors

saden1

1,584 commits

claude

475 commits

dependabot[bot]

10 commits

Languages

JavaScript

84.3%

CSS

14.0%

HTML

1.1%