Skip to content

LFM 1.0 — the specification

Version: 1.0-draft (pre-ratification) · Status: Draft · Date: 2026-06-13 License: CC-BY-4.0

LFM (Lattice-Flavored Markdown) is the authoring dialect of the Lattice slide engine. Its name follows GitHub-Flavored Markdown: LFM is a profile of Markdown, not a new language.

LFM = CommonMark + GFM task lists + the Lattice extension set.

A document written in LFM is a valid Markdown document. Every LFM extension MUST render readably in a CommonMark viewer that has no knowledge of Lattice — GitHub, GitLab, Bitbucket, and Confluence included. That property is graceful degradation, the governing rule of the standard and the criterion for adopting any future extension (§5).

This document specifies what LFM adds to Markdown, how each addition degrades, and what makes a document conformant. It does not specify a parser: LFM rides CommonMark, and any conformant CommonMark/GFM parser plus the extension handlers below is a conformant LFM implementation.


LFM defines three levels so a tool can advertise precisely what it supports.

LevelNameWhat it means
L0Markdown-faithfulThe document is valid CommonMark + GFM. An LFM-unaware renderer produces a readable result. Every conformant LFM document is L0.
L1Lattice-structuredThe renderer understands the slide and block extensions (§2–§3): _class directives, front-matter directives, the card grammar, and state markers. It produces Lattice layouts.
L2Lattice-diagnosticThe tool additionally implements the Diagnostic Protocol: it validates a document, reports findings with stable rule IDs, and offers machine-applicable fixes.

A conformant LFM document is valid at L0 and follows the grammar in §2–§4 for every Lattice construct it uses. A conformant LFM renderer declares the highest level it implements.

A slide selects a Lattice component with a Marpit class directive — an HTML comment, so it is invisible in every Markdown renderer:

<!-- _class: split-compare -->

The value is a space-separated list of one component name plus zero or more modifier tokens (§4). The complete, machine-readable vocabulary of names, modifiers, slots, and skeletons is published in dist/docs/grammar.json, generated from the component manifests.

  • Degrades to: nothing visible (HTML comment). L0-clean.

Slides are separated by a thematic break on its own line (---). This is CommonMark <hr>; an unaware renderer shows horizontal rules between sections.

  • Degrades to: horizontal rules. L0-clean.

A leading YAML front-matter block carries deck-wide directives. LFM recognises, in addition to Marpit’s own (theme, paginate, _class, …):

KeyValueEffect
finish:boardroom | sketch | sketch-cleanDeck-wide visual register. Unknown values are a diagnostic (§ unknown-finish).
logo:pathDeck logo injected into the masthead.

These two keys are the complete LFM-added front-matter surface in 1.0; every other recognised key belongs to Marpit.

  • Degrades to: front matter is hidden or shown as a metadata table, depending on the host. L0-clean (it is YAML front matter, which every GFM host already special-cases).

Card-style and ledger layouts read a title + body from a nested list (a card-style layout is one whose grammar declares a card slot; the set is published in dist/docs/grammar.json). The canonical shape is:

- Title
- body text

The inline form - **Title.** body is not valid LFM on a card-style layout: the body inherits the title’s weight. This is enforced as a diagnostic (card-style-inline-title, with an autofix). Ledger/numbered layouts use the ordered form:

1. Name
- body text
  • Degrades to: a nested bullet list — exactly what it reads as. L0-clean.

A single-character marker at the start of a list item encodes a status. The grammar is shared across checklist, verdict-grid, obligation-matrix, roadmap, and pricing:

MarkerSemanticNotes
[x]pass / done / metGFM task-list syntax.
[ ]neutral or not-metContext-dependent: todo/planned/exempt in checklist/roadmap/obligation-matrix; not-met in verdict-grid. GFM task-list syntax.
[-]partial / warnNot GFM syntax (see §5.1).
[/]skip / out-of-scopeNot GFM syntax (see §5.1).
  • [x] / [ ] degrade to: real checkboxes in any GFM host. L0-clean.
  • [-] / [/] degrade to: literal text ([-] …). Readable but not a checkbox — LFM 1.0’s only non-GFM-clean construct (§5.1).

3.3 Fenced sub-languages (charts & diagrams)

Section titled “3.3 Fenced sub-languages (charts & diagrams)”

A fenced code block with a recognised info string is a sub-language. Each is its own mini-spec; LFM only requires that it degrades to a code block.

Info stringSub-languageDegrades to
functionplotA function-plot graph spec (JSON body: mathematical functions + axes). Used today by the math component’s canvas variant to draw a curve beside an equation.A code block showing the JSON config. L0-clean.
mermaidMermaid (passthrough), used by the diagram component.A code block. L0-clean (and Mermaid-aware hosts render the diagram).

Each fence is named after the library that renders it: functionplot for function-plot, mermaid for Mermaid. This matches how the equation half of the math component uses standard $$…$$ to pass through to KaTeX. LFM does not rebrand these libraries or claim to own their config languages.

What LFM owns is narrow and consistent across all three: the fence registration, the SVG theming that makes the output inherit the deck’s palette tokens, and the degradation contract. The fence body is the renderer library’s own configuration language, not Markdown, and LFM does not own its schema. dist/docs/grammar.json records each fence’s library, component, and deprecated aliases; the config schema stays with that library, so this prose spec stays stable as those options evolve.

Deprecated alias. The functionplot fence was previously named latticeplot. That name implied a Lattice-owned grammar that does not exist (the body is function-plot’s, verbatim), so it was renamed for honesty. ```latticeplot is accepted as a deprecated alias for one release and will be removed in a future major version. See the 2026-06-13-lfm-standard decision note.

  • Eyebrow / subtitle / hero number and similar slot conventions are ordinary Markdown (a leading inline-code span, an <em>-wrapped unit, an <h2> lifted into a panel). They are structural reads over standard Markdown, documented per component in grammar.json, and are L0-clean by construction.

An HTML comment on a slide that is neither a directive nor a tooling pragma is that slide’s speaker note. This is Marp’s own semantics, unchanged:

# The slide title
<!-- Open cold. Pause two seconds before the first word. -->

A slide may carry several note comments; they are concatenated in order. Two comment kinds are not notes, matching Marpit exactly:

  • Directives<!-- _class: … -->, <!-- paginate: true -->, and any comment that parses as a block of known directive keys (§2.1, §2.3).
  • Tooling pragmasmarkdownlint-*, prettier-ignore[-start|-end], and lint disable|enable|ignore … (the editor/formatter control comments). The recognised set is Marpit’s magicCommentMatchers, mirrored in the reference implementation lib/authoring/notes-core.js so every render path agrees on the note/non-note boundary.

A conformant L1 renderer SHOULD surface notes through whatever presenter channel it offers. How a note is presented is implementation-defined; what counts as a note is the contract above, and that boundary is what conformance fixes. Among the reference render paths today, Lattice’s emulator materialises notes — a per-page PDF text annotation plus a hidden aside.lattice-notes element in the HTML sidecar; under marp-cli the same comments are marp-core’s native notes (surfaced by marp --pdf-notes or a PPTX export); the VS Code preview does not surface them yet.

The directive and tooling-pragma exclusions above are the pinned part of the contract: they are identical across implementations (the reference implementation pins them against marp-core’s documented comment-collection behavior — hardcoded expected outputs, not a live marp-core comparison; see test/unit/authoring/notes-core.test.js). A few rare comment-placement cases are explicitly not guaranteed identical, because they depend on how a given parser segments comments rather than on the note boundary: a comment buried inside a raw HTML block, two comments with no blank line between them, and a single comment that mixes a directive with prose. A conformant tool MAY collect these differently; authors should keep a note in its own comment, on its own line.

  • Degrades to: nothing visible — an HTML comment is invisible in every Markdown renderer, and on GitHub/GitLab a comment is already how an author leaves an out-of-band remark. L0-clean.

_class tokens are one component name plus modifier tokens. The full list is generated, not enumerated here, so it cannot drift:

  • Component namesdist/docs/components.json (vocabularies.buckets and the per-component entries) and the human catalog dist/docs/components.md.
  • Per-component grammardist/docs/grammar.json: for each component, its _class token, its required and optional slots (CSS selector + description), its skeleton, and the fences/state-markers it reads.
  • Modifiers — recognised by set membership or by these prefix families: tint-, mark-, with-, at-, no-, tone-, treatment-, checks-, plus the universal/semi-universal variant sets. The authoritative prefix list is MODIFIER_PREFIXES in lib/authoring/lint-core.js; the enumerated variant tiers are catalogued in design/design-system.md §6.5.

An unrecognised token is a diagnostic (unknown-class) with a did-you-mean suggestion, never a hard parse error — an LFM document with a typo is still a valid Markdown document.

5. Graceful degradation — the governing rule

Section titled “5. Graceful degradation — the governing rule”

Every LFM extension MUST render as readable Markdown in an LFM-unaware renderer. This is the property that makes LFM safe for a host like GitHub to render, and safe for an author to write without lock-in.

Rule for future extensions: no construct may be added to LFM without a row in the degradation table (§2–§3) stating exactly what an unaware renderer shows. If a construct cannot degrade readably, it is rejected or redesigned.

LFM 1.0 has exactly one: the [-] and [/] state markers (§3.2). GFM task-list syntax covers only [x] and [ ], so [-]/[/] render as literal text in a vanilla GFM host. They are retained because the four-state grammar is load-bearing across five components and the literal-text fallback is readable. A future LFM version MAY introduce GFM-clean aliases. Until then, this is a documented exception, not a conformance failure.

  • CommonMark — LFM is a strict superset. Every CommonMark document is a valid (if Lattice-inert) LFM document.
  • GFM — LFM adopts GFM task lists and tables. The only divergence is §5.1.
  • Marpit / Marp — LFM’s slide model (_class, --- separators, front matter) is Marpit-compatible; Lattice’s own engine natively re-implements that model (zero @marp-team runtime dependency — see engineering/marp-independence.md), it isn’t a Marp wrapper. Marpit gives you slides and a directive syntax; LFM adds what Marpit does not specify — a named, versioned, conformance-levelled contract, the degradation guarantee (§5), a fixed component/modifier vocabulary (§4), and a stable diagnostic protocol. LFM names and constrains the subset that produces boardroom layouts.
  • Mermaid — embedded as a fenced sub-language (§3.3), unchanged.

LFM follows SemVer at the standard level, tracking the engine’s stability contract: layouts and tokens are stable surfaces (see CHANGELOG.md).

  • Major — removing or breaking an extension, or breaking degradation.
  • Minor — adding an extension (with its degradation row) or a component.
  • Patch — clarifications and editorial fixes.

The machine-readable surface (grammar.json, components.json) is regenerated from the manifests on every build, so it always matches the engine that ships alongside this document.

Conformance is verifiable at each level, with a distinct test shape per level:

  • L0 (degradation). A fixture pairs an LFM source fragment with the readable Markdown a Lattice-unaware renderer MUST produce. The degradation rows in §2–§3 are the normative seed for this corpus.
  • L1 (structure). A fixture pairs an LFM document with the components and slots a renderer MUST resolve from it — the _class token, the card-grammar reads, the state-marker semantics. The per-component grammar in dist/docs/grammar.json is the oracle.
  • L2 (diagnostics). A fixture pairs an LFM document with the exact set of findings (diagnostics §1) a tool MUST emit: rule ID, severity, slide, and any autofix result. The reference implementation’s lint fixtures are the seed and its output is the v1 oracle.

A formal, runnable conformance suite is forthcoming — step 2 of the adoption path in the 2026-06-13-lfm-standard decision note. Until it ships, the degradation tables in §2–§3 and the reference implementation’s fixtures are the conformance contract.

LFM itself executes nothing: the prose layer is CommonMark, and every extension degrades to valid Markdown. The security surface is the fenced sub-languages a host renders (§3.3): Mermaid and function-plot both produce SVG from author-supplied configuration. A host that renders untrusted LFM MUST treat them as it treats any embedded renderer.

  • Sanitise the rendered SVG. Strip scripts, foreign objects, and event handlers before inlining the output — exactly as a host already does for a mermaid block.
  • Bound the work. A fence body is attacker-controllable. A host SHOULD cap render time and output size to resist denial-of-service from a pathological config.
  • Resolve asset paths under the host’s policy. The logo: directive (§2.3) and any local-file reference resolve against the host’s asset policy, not LFM’s; a host MUST NOT widen its file-access surface to render LFM. The Lattice reference engine gates local files behind an explicit --allow-local-files flag.

The component, modifier, and fence vocabulary is closed to the manifest set in LFM 1.0: a third party cannot register a new _class component, modifier prefix, or fence into the standard at authoring time. New vocabulary enters LFM the way every current entry did — a component manifest (or a fence registration) is added to the engine, and the build projects it into grammar.json, so the published grammar and the linter vocabulary update together and cannot drift (§4).

An unrecognised token is therefore a diagnostic (unknown-class), never a parse error: an author who reaches for vocabulary that does not exist is warned, not blocked. A registration path for out-of-tree vocabulary — an org-private modifier family, a third-party fence such as vega-lite — is deferred (§11).

LFM 1.0 deliberately does not:

  • Define a parser or a formal grammar (ABNF/EBNF) for the prose layer. LFM rides CommonMark; the parser is CommonMark’s. grammar.json is a structural projection (selectors + skeletons), not a parser grammar.
  • Re-encode the [-] / [/] state markers into GFM-clean glyphs (§5.1). The current glyphs are documented, not changed, in 1.0.
  • Open a registration path for out-of-tree components, modifiers, or fences (§10).
  • Ship a Language Server or character-precise diagnostic ranges. The diagnostic protocol is their foundation; see diagnostics §6.
  • License. This specification — the prose under spec/ — is published under CC-BY-4.0. Anyone may implement LFM (a renderer, a linter, a remark plugin) and redistribute the spec, with attribution. The Lattice engine’s code is AGPL-3.0; the spec carries its own license because a normative document is a different artifact from the reference implementation.
  • Steward. The SlideWright project stewards LFM. The spec is the owned asset; conformant implementations are interchangeable, and a second implementation is an explicit adoption goal.
  • Amendments. Changes follow the SemVer rules in §7 and land through the project’s normal review — a PR against spec/, recorded in the history below and in CHANGELOG.md. Any change to a normative MUST/MAY requires a version bump per §7.
VersionDateChange
1.0-draft2026-06-13Initial draft. Formalises the existing extension set, conformance levels, degradation table, and the companion diagnostic protocol.

Appendix A — A worked example (informative)

Section titled “Appendix A — A worked example (informative)”

A three-slide LFM deck, and how the same source reads in each environment:

---
theme: indaco
finish: boardroom
paginate: true
---
<!-- _class: title -->
# Q3 Strategy
`Board review · 2026-06-13`
How we get to plan by year-end.
---
<!-- _class: checklist -->
## Readiness
- [x] Revenue plan signed
- [-] Hiring partially staffed
- [ ] Security audit scheduled
---
<!-- _class: cards-grid -->
## Bets
- Expand EU
- Two markets live by Q4.
- Ship the API
- Private beta with six design partners.

In a Lattice-unaware viewer (GitHub, plain CommonMark):

  • The front matter is hidden or shown as a metadata table.
  • The _class comments are invisible.
  • --- renders as horizontal rules between sections.
  • [x] and [ ] render as real checkboxes; [-] renders as the literal text [-] (§5.1).
  • The eyebrow code-span and the “Bets” nested list render as ordinary inline code and a nested bullet list.

A reviewer on GitHub sees a titled, sectioned checklist and a list of bets — the document is completely readable with no Lattice support.

In Lattice (L1+):

  • Slide 1 renders the title component: display headline, eyebrow, standfirst.
  • Slide 2 renders checklist, mapping [x] / [-] / [ ] to pass / partial / todo states.
  • Slide 3 renders cards-grid, lifting each card’s first line to a bold title above its body.

Nothing in the source is lost on the way down, and nothing Lattice-specific leaks up into the degraded view.


This spec is generated-adjacent: tools/build-docs-portal.js builds the vocabulary and per-component grammar it points to (dist/docs/grammar.json, dist/docs/components.json) from the component manifests. This prose is the stable contract; the machine artifacts are the always-current detail.