Campfire

Layouts

Narrative structures, not grids

Layouts are React components in layouts/**/*.tsx — yours and registry-installed ones side by side. A slide selects one by name:

layout: problem-solution

resolves the layout file named problem-solution.tsx anywhere under layouts/. Names form a flat namespace; if two files share a name, the shallower path wins and Campfire reports a layout-collision warning.

The contract

Layouts receive a deliberately tiny prop surface:

type LayoutProps = {
  title?: string
  children: React.ReactNode
}
import type { ReactNode } from "react";

export default function TitleLayout({
  title,
  children,
}: {
  title?: string;
  children: ReactNode;
}) {
  return (
    <main className="flex h-full flex-col justify-center p-24">
      {title ? <h1 className="font-bold text-7xl">{title}</h1> : null}
      <div className="mt-8 text-2xl">{children}</div>
    </main>
  );
}

The canvas

Slides render on a fixed logical canvas — 1280×720 by default — that the runtime scales to fit every surface: overview preview, present mode, a projector. Design against absolute canvas coordinates (h-full, p-24, text-7xl) and the type scale stays consistent everywhere.

Change it in campfire.config.ts:

export default {
  canvas: { width: 1920, height: 1080 },
};

Rules

  • Layouts may import anything; slides import nothing.
  • Layouts stay pure: no presentation state, no runtime hooks. Current slide, progress, and navigation belong to the runtime — that keeps registry layouts portable.
  • Prefer narrative names (title, agenda, section, problem-solution, statement, closing) over generic grids.

On this page