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.
1. Conformance levels
Section titled “1. Conformance levels”LFM defines three levels so a tool can advertise precisely what it supports.
| Level | Name | What it means |
|---|---|---|
| L0 | Markdown-faithful | The document is valid CommonMark + GFM. An LFM-unaware renderer produces a readable result. Every conformant LFM document is L0. |
| L1 | Lattice-structured | The renderer understands the slide and block extensions (§2–§3): _class directives, front-matter directives, the card grammar, and state markers. It produces Lattice layouts. |
| L2 | Lattice-diagnostic | The 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.
2. Slide-level extensions
Section titled “2. Slide-level extensions”2.1 The _class directive
Section titled “2.1 The _class directive”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.
2.2 Slide separators
Section titled “2.2 Slide separators”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.
2.3 Front-matter directives
Section titled “2.3 Front-matter directives”A leading YAML front-matter block carries deck-wide directives. LFM recognises,
in addition to Marpit’s own (theme, paginate, _class, …):
| Key | Value | Effect |
|---|---|---|
finish: | boardroom | sketch | sketch-clean | Deck-wide visual register. Unknown values are a diagnostic (§ unknown-finish). |
logo: | path | Deck 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).
3. Block-level extensions
Section titled “3. Block-level extensions”3.1 The card grammar (nested-list slots)
Section titled “3.1 The card grammar (nested-list slots)”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 textThe 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.
3.2 State markers
Section titled “3.2 State markers”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:
| Marker | Semantic | Notes |
|---|---|---|
[x] | pass / done / met | GFM task-list syntax. |
[ ] | neutral or not-met | Context-dependent: todo/planned/exempt in checklist/roadmap/obligation-matrix; not-met in verdict-grid. GFM task-list syntax. |
[-] | partial / warn | Not GFM syntax (see §5.1). |
[/] | skip / out-of-scope | Not 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 string | Sub-language | Degrades to |
|---|---|---|
functionplot | A 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. |
mermaid | Mermaid (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
functionplotfence was previously namedlatticeplot. That name implied a Lattice-owned grammar that does not exist (the body is function-plot’s, verbatim), so it was renamed for honesty.```latticeplotis accepted as a deprecated alias for one release and will be removed in a future major version. See the2026-06-13-lfm-standarddecision note.
3.4 Other inline conventions
Section titled “3.4 Other inline conventions”- 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 ingrammar.json, and are L0-clean by construction.
3.5 Speaker notes
Section titled “3.5 Speaker notes”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 pragmas —
markdownlint-*,prettier-ignore[-start|-end], andlint disable|enable|ignore …(the editor/formatter control comments). The recognised set is Marpit’smagicCommentMatchers, mirrored in the reference implementationlib/authoring/notes-core.jsso 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.
4. The component & modifier vocabulary
Section titled “4. The component & modifier vocabulary”_class tokens are one component name plus modifier tokens. The full list
is generated, not enumerated here, so it cannot drift:
- Component names —
dist/docs/components.json(vocabularies.bucketsand the per-component entries) and the human catalogdist/docs/components.md. - Per-component grammar —
dist/docs/grammar.json: for each component, its_classtoken, 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 isMODIFIER_PREFIXESinlib/authoring/lint-core.js; the enumerated variant tiers are catalogued indesign/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.
5.1 Known non-GFM-clean constructs
Section titled “5.1 Known non-GFM-clean constructs”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.
6. Relationship to other standards
Section titled “6. Relationship to other standards”- 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-teamruntime dependency — seeengineering/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.
7. Versioning
Section titled “7. Versioning”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.
8. Conformance testing
Section titled “8. Conformance testing”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
_classtoken, the card-grammar reads, the state-marker semantics. The per-component grammar indist/docs/grammar.jsonis 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.
9. Security considerations
Section titled “9. Security considerations”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
mermaidblock. - 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-filesflag.
10. Adding to the vocabulary
Section titled “10. Adding to the vocabulary”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).
11. Non-goals (LFM 1.0)
Section titled “11. Non-goals (LFM 1.0)”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.jsonis 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.
12. Governance & license
Section titled “12. Governance & license”- License. This specification — the prose under
spec/— is published under CC-BY-4.0. Anyone may implement LFM (a renderer, a linter, aremarkplugin) 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 inCHANGELOG.md. Any change to a normative MUST/MAY requires a version bump per §7.
Document history
Section titled “Document history”| Version | Date | Change |
|---|---|---|
| 1.0-draft | 2026-06-13 | Initial 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: indacofinish: boardroompaginate: 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
_classcomments 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
titlecomponent: 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.