# FrameSeq > FrameSeq is a declarative TypeScript framework for creating editable presentations. A linear `.slides.ts` source renders to interactive HTML and exports to PDF and editable PPTX. Canonical documentation: https://github.com/pride7/frameseq/tree/main/docs AI generation guide: https://github.com/pride7/frameseq/blob/main/docs/ai-generation.md API reference: https://github.com/pride7/frameseq/blob/main/docs/api-reference.md Live Gallery: https://pride7.github.io/frameseq/ ## Create a project ```bash npm create frameseq@latest my-talk cd my-talk npm install npm run dev ``` The main source file is `slides.ts`. Generated projects also contain `components/content.ts` for reusable content functions and `components/theme.ts` for project design settings. The entry imports those two local modules directly; there is no `components/index.ts`. FrameSeq authoring functions are global inside `slides.ts`: do not import FrameSeq itself, add wrapper callbacks, or add an export statement there. Ordinary component modules must explicitly import the FrameSeq functions they use from `@pride7/frameseq`. ## Minimal valid source ```ts presentation({ title: "My presentation", author: "Your name", theme: "minimal-academic", }); slide().cover(); slide("One clear idea"); text("State the idea in one sentence.").lead(); bullets( "First supporting point", "Second supporting point", "Third supporting point", ); ``` ## Authoring model - Call `presentation()` once before any slide. - Each `slide()` call starts a new slide. - Every command after `slide()` belongs to that slide until the next `slide()` call. - Content functions create objects. Chain modifiers on the returned object to control it. - Prefer semantic layouts and roles first. Use low-level styling or exact positioning only when the composition requires it. - Keep the source linear. Do not recreate HTML nesting in TypeScript. ```ts slide("Architecture").split("40:60"); text("Input").lead(); bullets("Readable source", "Typed API"); right(); text("Output") .size(32) .bold() .color("#2563eb"); ``` ## Core API Presentation: `presentation(options)` Slides: `slide(nameOrOptions)`, `.cover()`, `.split(ratio)`, `.grid(columns, gap?)`, `.center()`, `.fullBleed(src, alt?)`, `.canvas()`, `.notes(content)`, `note(content)` Content: `text()`, `bullets()`, `steps()`, `metric()`, `card()`, `group()`, `ref()`, `gridSection()`, `image()`, `code()`, `math`, `typst`, `typstFile()`, `latex`, `latexFile()`, `rect()`, `circle()`, `line()`, `spacer()` Diagram references: `.as(name)`, `.rightOf()`, `.leftOf()`, `.above()`, `.below()`, `.centerOn()`, `.alignTop()`, `.alignLeft()`, `line().from(ref)`, `line().to(ref)` Regions: `main()`, `left()`, `right()`, `cell(index)`, `at(path)`, `gap(rows, columns?)` Common modifiers: `.hero()`, `.subtitle()`, `.lead()`, `.caption()`, `.quote()`, `.size()`, `.weight()`, `.bold()`, `.color()`, `.background()`, `.width()`, `.height()`, `.maxWidth()`, `.padding()`, `.margin()`, `.selfAlign()`, `.centerSelf()`, `.position()`, `.style()`, `.showAt()` Layout axes: `align()` and `selfAlign()` move objects **across** a region (horizontally in a column, vertically in a row); `justify()` distributes them **along** it. To push one object to the far end, put `spacer()` before it. `align()`, `justify()`, `gap()`, and `wrap()` need the object itself to be a `row()`/`column()`/grid; `selfAlign()`, `grow()`, and `spacer()` need its container to be one. Anywhere else the browser ignores them and `frameseq check` reports `inert-modifier`. Centre one object inside its region with `.centerSelf()` (or `.selfAlign("start" | "center" | "end" | "stretch")`) instead of centring the whole region with `.center()`. It needs a size of its own: `.width()` in a column, `.height()` in a row. `.textAlign("center")` centres the words inside an object rather than the object itself. Built-in themes: `blank`, `midnight`, `paper`, `beamer-default`, `beamer-madrid`, `beamer-cambridge-us`, `minimal-academic` Chinese, Japanese, and Korean content needs no configuration: every theme font stack ends with CJK families and resolves per character, so Latin text keeps the theme font. Override the face with `presentation({ font: { family: "..." } })`. The rendering machine must have a CJK font installed for PDF, PPTX, and Typst export. ## Layout patterns Use a split for comparison or text beside a visual: ```ts slide("Comparison").split("45:55"); text("Before").lead(); bullets("Slow", "Manual"); right(); text("After").lead(); bullets("Fast", "Repeatable"); ``` Use a grid for parallel facts: ```ts slide("Results").grid(3); cell(0); metric("42%", "Faster"); cell(1); metric("18K", "Samples"); cell(2); metric("3", "Outputs"); cell(3); metric("4.8", "Rating"); // cells past the first row wrap onto the next one ``` Use a region path when one region needs its own group of objects. Each segment is a container that is created on first use, so grouping never needs nested source. The first segment may be `main`, `left`, `right`, or `cell`; revisiting a path appends to the same region; paths reset with the next `slide()`: ```ts slide("Roadmap").grid(2); at("cell0/now").card(); text("Q3").eyebrow(); bullets("Shipped", "Measured"); at("cell1/next").card(); text("Q4").eyebrow(); ``` Use a local grid when normal content should remain above and below a grid. Each supplied object becomes one cell; do not manually select cells: ```ts text("Quarterly results"); gridSection( 3, metric("42%", "Faster"), metric("18K", "Samples"), metric("3", "Outputs"), ).gap(20); text("All targets were exceeded."); ``` Use `card(title, content)` for a common card and `group(...items).card()` when one cell needs multiple styled objects. `group()` and `gridSection()` also accept names, and `ref(name)` selects a named object again, so grouping and later styling need no local variables: ```ts rect("Parse").as("parse"); rect("Build").as("build"); group("parse", "build").row().gap(16); ref("parse").fill("#dbeafe"); ``` Use a canvas only for diagrams or deliberate freeform composition: ```ts slide({ name: "System map" }).canvas(); text("Compiler") .position({ x: 80, y: 90 }) .width(320) .size(32) .bold(); ``` Plain numeric lengths are pixels on the fixed presentation canvas. The default canvas is 1280 by 720 with a 16:9 ratio. In a diagram, position one object and describe the rest relative to it. Name objects with `.as(name)`, place them with `.rightOf()`, `.leftOf()`, `.above()`, `.below()`, `.centerOn()`, `.alignTop()`, `.alignLeft()`, and connect them with `line().from(name).to(name)`: ```ts slide({ name: "Pipeline" }).canvas(); rect("Encoder").as("enc").position({ x: 80, y: 140 }).width(200).height(100); rect("Decoder").as("dec").rightOf("enc", 140); line().from("enc").to("dec").arrow("end"); ``` Better still, put the objects in a row or column so none of them needs a coordinate, and anchor the container itself: ```ts slide({ name: "Pipeline" }).canvas(); at("stages").row().gap(80).anchor("center"); rect("Encoder").as("enc"); rect("Decoder").as("dec"); line().from("enc").to("dec").arrow("end"); ``` Write the connectors inside the same container. A resolved row or column needs an explicit `gap()` and children with known sizes; `wrap()`, `grow()`, and text of unknown height are refused with an error. Names are unique per slide and may be referenced before they are defined. `from()` and `to()` accept an anchor, such as `"enc.right"` or `"enc.bottom-left"`; without one FrameSeq uses the facing edges. Referenced objects must sit on the same canvas and have a resolvable box: `rect()` defaults to 240 by 96, `circle()` to 160 by 160. ## Math, Typst, LaTeX, and Tailwind Use a tagged `text` template for inline LaTeX math: ```ts text`Euler's identity is $e^{i\pi} + 1 = 0$.`; ``` Use the `math` tag for a standalone equation: ```ts math`\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}`; ``` Use Typst only for a complex local fragment while FrameSeq keeps control of slide structure. Static Typst templates do not support JavaScript interpolation: ```ts typst` #set text(size: 22pt) $ min_theta sum_(i=1)^n loss(f_theta(x_i), y_i) $ `.width(720); ``` Typst requires the optional `@myriaddreamin/typst-ts-node-compiler` development dependency. Use static LaTeX fragments when an existing `tabular` table should be preserved. The optional `node-tectonic` dependency is required, and complete LaTeX documents or JavaScript interpolation are not accepted: ```ts latex` \begin{tabular}{lr} \toprule Model & Accuracy \\ \midrule FrameSeq & \textbf{94.6\%} \\ \bottomrule \end{tabular} `.width(720); ``` Tailwind utilities are available through `.style()` without configuration. Keep utility names as complete static strings: ```ts text("Important") .style("rounded-2xl bg-slate-900 px-8 py-5 text-3xl font-semibold text-white"); ``` ## Generation rules - Give every slide one primary message. - Use short visible copy; move delivery detail into `.notes()` or `note()`. Repeated `note()` calls append a line to the current slide. - Prefer `split()`, `grid()`, `gridSection()`, and `center()` over coordinates. - Use `canvas()` and `.position()` for diagrams, not ordinary text pages. - In a diagram, position one object, then name objects and place and connect the rest relative to it. - Use text roles and a built-in theme before adding local style overrides. - Do not invent image paths. Use supplied assets or omit the image. - Preserve source citations and label illustrative or synthetic data clearly. - Use tagged templates for LaTeX so backslashes remain intact. - Never interpolate dynamic values into `typst` templates. - Keep `latex` templates static and body-only; use `latexFile()` for larger table fragments. - Do not construct Tailwind class names from fragments. - After generation, run the type and rendered-layout checks, then correct every error and unintended warning. ## Validate and export Generated projects provide these commands: ```bash npm run check npm run build npm run pdf npm run pptx npm run typst ``` `npm run typst` emits editable `.typ` source. Inline `$...$` formulas become MiTeX `mi()` calls, display `math()` becomes `mitex()`, basic LaTeX text uses `mitext()`, common LaTeX `tabular` fragments become native Typst tables, and native Typst fragments remain source objects. For a machine-readable layout report: ```bash npx frameseq check slides.ts --json ``` For a fast source outline without starting a browser: ```bash npx frameseq inspect slides.ts --json ``` The inspect report includes slide labels, layouts, object types, notes, and source locations. Use it to navigate or plan edits; use `check --json` for measured rendered-layout validation. Fix `canvas-overflow` and `text-clipped` errors. Add visible content when `empty-slide` appears; use `slide().allowEmpty()` only for an intentional blank page. Treat `font-too-small` warnings as a reason to shorten content or restructure the slide before reducing type further. Treat `empty-region` and `similar-name` warnings as mistyped `at()` paths or `.as()` names and correct the spelling instead of adding content to the wrong region. ## Suggested agent workflow 1. Read this file and the user's brief. 2. Choose a single visual theme and outline one message per slide. 3. Write the complete `slides.ts` with semantic layouts and speaker notes. 4. Run `npm run check`. 5. Revise the source using the reported slide label, object path, and measured overflow. 6. Repeat until the check passes. 7. Build or export only after validation succeeds. Full workflow and reusable prompts: https://github.com/pride7/frameseq/blob/main/docs/ai-generation.md