Skip to content

Your first deck

A deck is an ordered list of slide modules handed to <Present>. Each module’s default export is the slide; an optional notes export shows in the presenter view.

slides/01-intro.tsx
import { Step } from "@liebstoeckel/engine";
export const notes = (
<div>
<p>Open strong. Reveal each point with <kbd></kbd>.</p>
</div>
);
export default function Intro() {
return (
<div className="w-full">
<h1 className="font-heading text-7xl font-semibold text-text">
Decks that <span className="italic text-primary">talk back</span>.
</h1>
<div className="mt-8 space-y-3 font-body text-2xl text-muted">
<Step>Plugins are real Bun packages.</Step>
<Step>State is shared live over a CRDT.</Step>
</div>
</div>
);
}

Register them in order:

main.tsx
import Intro from "./slides/01-intro";
import Agenda from "./slides/02-agenda.mdx";
<Present brands={["liebstoeckel"]} slides={[Intro, Agenda]} />

Wrap anything in <Step> to reveal it on the next press. Steps reveal in document order; once the last step is shown, the next press advances the slide.

<Step>First this…</Step>
<Step>…then this.</Step>

The presenter view shows a prominent step counter so you always know whether a press will reveal or advance.

Reveals replay in reverse when you go backwards. Pressing hides the last revealed step, and once a slide is fully hidden the press before that takes you to the previous slide with all of its reveals already shown — the state your audience last saw it in, the same way PowerPoint and Keynote behave. Going forwards is the mirror image: you enter each slide unrevealed and build it up.

Jumping is different from stepping, and deliberately so. Picking a slide in the overview (o) or typing its number lands on that slide unrevealed, whichever slide you came from and however far into it you were.

On the last slide, once everything is revealed, the next press ends the deck rather than advancing. The deck shows a deliberate end card — or Esc goes back into the deck, o opens the overview, r restarts.

Ending is a property of the deck, not of one window, so you can do it from the presenter view and the audience screen follows. The presenter’s forward button reads End → for that last press and then Ended, and Back takes both windows out of the end card and returns you to the final slide with its reveals intact.

Export notes (plain JSX) from a slide module. The presenter window renders it in a styled panel alongside the current/next slide previews and a timer.

export const notes = (
<div>
<p>Key point. Then mention the <strong>demo</strong>.</p>
<ul><li>Press <kbd>Q</kbd> to show the QR.</li></ul>
</div>
);

<Magic> morphs one slide’s element into the next slide’s element with the same id. Motion FLIP-animates position and size and crossfades the content. Declare each one inline on its slide; the two can differ (text → text, small → large), so it’s true Keynote-style Magic Move.

import { Magic } from "@liebstoeckel/engine";
// On slide 1:
<Magic id="logo" className="text-4xl"></Magic>
// On slide 2, same id, new place/size (and even new content) → it morphs there:
<Magic id="logo" className="text-8xl absolute top-10 right-10"></Magic>

Comments

liebstoeckel

Code-first presentations your agent can author. One file out, no server.

© 2026 Leon Kaucher