Document model#
FrameSeq slide files are evaluated from top to bottom. Authoring commands operate on a small amount of document context: the current presentation, current slide, and current layout region.
Start the presentation#
presentation("My Talk");
This creates a presentation titled My Talk and makes it the active document. The title is document metadata and becomes the browser page title. It does not create a visible cover or add text to a slide.
The object form configures the canvas:
presentation({
title: "My Talk",
subtitle: "A short description",
author: "Your Name",
institute: "Your Institute",
date: "2026",
ratio: "16:9",
width: 1280,
theme: "midnight",
font: {
family: '"Noto Sans SC", sans-serif',
size: 24,
lineHeight: 1.5,
heading: {
family: '"Noto Serif SC", serif',
weight: 700,
},
code: {
family: '"JetBrains Mono", monospace',
size: 18,
},
},
});
Without theme, FrameSeq uses the neutral white blank theme. See Themes to select a built-in theme or define your own. The older background option remains available as a shortcut for changing only the slide canvas color.
subtitle, author, institute, and date are optional document metadata. Themes may use them differently: beamer-madrid puts author, institute, and date in the footer, while minimal-academic uses all four fields to generate an empty cover slide automatically.
ratio accepts "16:9" or "4:3". The default width is 1280; FrameSeq derives the height from the ratio unless height is provided.
font is an optional presentation-wide typography override. Top-level values set body typography; heading and code configure their respective categories. These settings override the selected theme, while modifiers on an individual object still have the highest priority.
A slide file should call presentation() once, before the first slide. Calling it again starts a new document context and abandons the previously active one.
Start a slide#
slide("Architecture");
This ends the previous slide, starts a new one, and adds a visible Architecture heading. All following content commands belong to this slide until another slide() call.
slide("Architecture");
text("Compiler");
bullets("Parser", "Renderer", "Exporter");
slide("Result");
metric("42%", "Growth");
No indentation or callback establishes ownership. Source order does.
Local layout functions keep that rule. gridSection() receives content objects directly, moves them into one local grid in argument order, and then returns authoring to the surrounding slide flow:
text("Before");
gridSection(3, card("A", "First"), card("B", "Second"), card("C", "Third"));
text("After");
Use group(...items) only when several independently styled objects need to become one grid item. The rendered document still has a parent-child tree, but ordinary slide source does not need to manage that tree manually.
Slide name and visible title#
The string form sets both the internal page name and visible title:
slide("Architecture");
Use the object form when those values should differ, or when a page should have no automatic heading:
slide({ name: "architecture", title: "System architecture" });
slide({ name: "cover" }).cover();
text("My Talk").hero();
nameidentifies the page but is not rendered.titlecreates the standard visible page heading.
Speaker notes#
Attach private notes to a slide with notes():
slide("Architecture")
.notes(`
Explain the three compiler stages.
Pause before revealing the result.
`);
note() writes the same metadata as a normal linear command, which keeps a reminder next to the content it belongs to:
slide("Architecture");
text("Three compiler stages").lead();
note("Explain the three compiler stages.");
steps("Parse", "Render", "Export");
note("Pause before revealing the result.");
Repeated note() calls append a line rather than replacing the previous text.
Notes belong to the slide as metadata. They are available in presenter view but are not rendered into the audience page or PDF.
Create a cover#
cover() changes the current slide to the cover layout. It does not invent cover content.
slide({ name: "Cover" }).cover();
text("FrameSeq").hero();
text("Build presentations like interfaces").subtitle();
text("Your name").author();
Content returns objects#
Content commands attach an object to the current region and return that same object. Modifiers can therefore be chained immediately:
text("A strong statement")
.size(pt(30))
.bold()
.color("#38bdf8");
Region context#
Structured layouts introduce regions. right() and cell() change the destination for subsequent content; they do not create content themselves.
slide("Comparison").split("40:60");
text("Left side");
right();
text("Right side");
A new slide() resets the destination. main() returns to the current slide's primary region: the left region of a split, the first cell of a grid, or the normal body.
at(path) extends the same idea to containers the layout does not own yet. Each segment of the path is a region; segments that do not exist are created when the path is first used, so grouping never requires nesting the source:
at("cell0/highlight").card();
text("Q3").eyebrow();
text("Anchors and region paths");
Revisiting a path selects the same region again and appends to it. Like the rest of the document context, paths belong to the current slide and reset with the next slide().
Zero-boilerplate compilation#
For the entry passed to frameseq dev, frameseq build, or frameseq pdf, FrameSeq automatically:
- imports the document commands;
- evaluates the file from top to bottom;
- retrieves the presentation created by
presentation(); - exports it to the renderer.
The low-level explicit object API does not use this stateful context. See advanced composition.