Skip to content
Deckard
Esc
navigateopen⌘Jpreview
On this page

@thebuilder/deckard-core

Every entry point the package exposes, what each one exports, and the four Next.js route adapters an app re-exports.

The package compiles to dist/ with tsc: ESM, .d.ts, and "use client" directives preserved. No bundler, so a stack trace still points at a file that matches the source. An app installs it and imports it like any other package, with no transpilePackages entry and no Tailwind @source of its own.

Entry points

Entry point Contents
@thebuilder/deckard-core defineDeck, slide resolution, summaries, theme helpers, every type
@thebuilder/deckard-core/components The runtime components and slide context hooks
@thebuilder/deckard-core/code-block CodeBlock, kept off the components barrel because shiki loads WebAssembly
@thebuilder/deckard-core/discovery discoverSlides
@thebuilder/deckard-core/next The four route adapters
@thebuilder/deckard-core/slide-from-module slideFromModule
@thebuilder/deckard-core/ui The shadcn primitives the runtime renders
@thebuilder/deckard-core/utils cn
@thebuilder/deckard-core/styles.css The --slide-* token contract

CodeBlock is split out because shiki pulls in a WebAssembly regex engine. Importing it through the components barrel would turn every discovered slide module async, which discovery refuses. A slide that shows highlighted code belongs in the slides array.

@thebuilder/deckard-core

Export What it does
defineDeck(config) Resolve a DeckConfig into a Deck.
resolveCanvas(partial) Fill in and validate the canvas config.
resolveSlides(slides, defaults, slidesPath) Resolve a slide array. slidesPath defaults to /slides; defineDeck passes its resolved route.
getSlideById(slides, id) Find a resolved slide by its id.
normalizeFooterMode(mode) Collapse "counter" into "visible".
toSlideSummary(slide) / toSlideSummaries(slides) The serializable shape client components receive.
resolveTheme(theme) Fill in and validate a SlideTheme.
toDeckPresentation(deck) The serializable slice the shell needs.
canSwitchColorMode(theme) / forcedColorMode(theme) What the color-mode toggle does for a given theme.
isPdfExport() / pdfExportColorMode() Read the export environment variables.
isCapturing() True while a capture tool is driving the page.
captureAttribute The attribute a capture tool sets on the document element.
motionField(theme, background) The field a theme paints a background variant with, or undefined.
slideBackgroundModes The background variants the runtime paints for every deck.
slideMotionFields The three fields a theme may name.
PRESENTER_CHANNEL_NAME The BroadcastChannel name presenter mode uses.

Types: Deck, DeckConfig, DeckCanvasConfig, DeckHeaderConfig, DeckFooterConfig, DeckPresentation, ResolvedSlide, SlideDefinition, SlideMeta, SlideModule, SlideComponent, SlideDefaults, SlideSummary, SlideTheme, SlideColorMode, SlideHeaderMode, SlideFooterMode, SlideFooterModeInput, SlideLayoutMode, SlideBackgroundMode, BuiltInSlideBackgroundMode, SlideMotionMode, SlideMotionField, PresenterChannelMessage, PresenterPreviewState, PresenterSlideState.

@thebuilder/deckard-core/components

Export What it does
SlideShell Assembles a slide: frame, canvas chrome, deck controls.
SlideViewport Fits the canvas to the browser window and publishes --deckard-scale.
SlideCanvas The fixed coordinate space slides are authored in.
SlideCanvasHeader, SlideCanvasFooter The themed chrome inside the canvas.
SlideBackground The element carrying data-slide-background, plus the motion canvas when the theme paints that variant with one.
SlideMotionBackground The animated canvas itself.
SlideScrollArea A scroll region that does not step the deck.
SlideStepper, SlideStep, SlideStepAdvanceArea, useSlideStepper Step reveals.
SlideCommandCenter The Cmd/Ctrl + K jump dialog.
DeckControls The corner cluster.
PresenterConsole, PresenterPopoutButton Presenter mode.
ColorModeProvider, SlideshowColorModeToggle Light and dark.
SlideContextProvider, useSlideTitle, useIsPresenterPreview Slide context.
SlideErrorBoundary The inline error card a throwing slide renders under next dev.
SlideMediaVideo A <video> that suppresses autoplay in the presenter preview.
SlidePrefetch Prefetches the neighbouring slides.
StaticMediaBoundary Freezes video and audio inside a slide the audience is not on, so a presenter preview never plays.

useSlideTitle

Returns the current slide’s authored title, or null. Layout blocks call it so a slide’s title stays defined in one place, and only take an explicit title prop when you want to override the text inside the layout.

useIsPresenterPreview

Returns true inside the presenter console’s preview iframe. Use it in a client component to skip autoplay, audio, canvas work, or anything else expensive.

Motion backgrounds

A theme paints most background variants in CSS. It paints a variant in an animated canvas by naming it in motion, keyed by the name a deck writes as background:

export const aurora: SlideTheme = {
  className: "aurora-theme",
  colorModes: ["dark", "light"],
  defaultColorMode: "dark",
  id: "aurora",
  motion: { closing: "waves", hero: "aurora", statement: "wash" },
}
{ background: "hero", body: <HeroSlide … />, slug: "opening" }

The three fields are aurora, banded curtains over noise; waves, a horizontal band that undulates; and wash, a soft noise field with a dither. Their colours and their speed come from the --slide-motion-* tokens, so the theme sets them per variant and per color mode in its stylesheet. A variant the theme never names is a CSS background like any other, and a deck whose theme names none never loads the WebGL runtime: it is fetched by the canvas component on mount.

One requestAnimationFrame loop drives every canvas on the page, and it skips a canvas that is off screen or has collapsed to nothing. Backing stores are half the canvas size in each axis.

Freezing

A frozen field still renders. It draws one frame at a fixed point in the animation and holds it, so two captures of the same slide are the same image. Five things freeze one:

Reason How it is decided
prefers-reduced-motion: reduce matchMedia, live: dropping the preference starts the field
A capture is running isCapturing(), which reads the PDF build flag and the captureAttribute marker the deckard capture commands set
The presenter preview The presenterPreview query the console’s iframes carry
The deck asked motion: "frozen" in defineDeck
The slide asked motion: "frozen" on the slide

The canvas reports which of the two it is doing on data-slide-motion-state, valued running or frozen.

When there is no WebGL

The canvas paints nothing, reports data-slide-motion-state="unavailable", and the background the theme painted underneath is the slide. The same holds while the runtime is loading and if the context is lost mid-presentation, so a theme paints a variant it animates as though it never would.

useSlideStepper

Returns { currentStep, stepCount, advance, retreat, canAdvance, canRetreat, isReadOnly }, or null outside a stepper.

Route adapters

@thebuilder/deckard-core/next ships the route logic, so an app owns its deck and re-exports the routes. Each adapter takes the resolved deck and returns the route pieces.

Adapter File Returns
createSlideRoute(deck) app/slides/[id]/page.tsx Page, generateMetadata, generateStaticParams
createPresenterPage(deck) app/presenter/page.tsx Page, metadata
createDeckSitemap(deck, { siteUrl }) app/sitemap.ts The sitemap function
createFirstSlideRedirect(deck) app/page.tsx Page, which redirects to the first slide
import { createSlideRoute } from "@thebuilder/deckard-core/next"
import { deck } from "@/deck/deck"

const { Page, generateMetadata, generateStaticParams } = createSlideRoute(deck)

export { generateMetadata, generateStaticParams }
export default Page
import { createPresenterPage } from "@thebuilder/deckard-core/next"
import { deck } from "@/deck/deck"

const { Page, metadata } = createPresenterPage(deck)

export { metadata }
export default Page
import { createDeckSitemap } from "@thebuilder/deckard-core/next"
import { deck } from "@/deck/deck"

export default createDeckSitemap(deck)
import { createFirstSlideRedirect } from "@thebuilder/deckard-core/next"
import { deck } from "@/deck/deck"

export default createFirstSlideRedirect(deck)

Every slide prerenders statically. The route reads no request: ?presenterPreview=1 and ?step= are read on the client, inside a Suspense boundary holding nothing but the reader.

createDeckSitemap falls back to NEXT_PUBLIC_SITE_URL and then to http://localhost:3000 when you pass no siteUrl. That sitemap is also how deckard screenshots, check-overflow, and export pdf find the slide list, so an app that drops the route breaks those three.

The components the adapters compose are exported too, so a deck that needs its own route can write one.

The dependency direction

@thebuilder/deckard-core holds nothing deck-specific. Colors, sizes, and backgrounds live in a theme, and a theme is data: the runtime depends on the token contract in styles.css and never on a preset.

@thebuilder/deckard-themes takes SlideTheme from core as a type-only import and names core a peer dependency, so nothing crosses at runtime and core does not know the themes exist.

Was this page helpful?