Skip to content
Deckard
Esc
↑↓navigate↵open⌘Jpreview
On this page

@thebuilder/deckard-core

Every entry point the package exposes, what each one exports, and the 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 route adapters
@thebuilder/deckard-core/share-card createSlideShareCard, kept off /next because next/og loads a WebAssembly renderer
@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.
deckSiteUrl(siteUrl?) The deck’s public origin: siteUrl, else NEXT_PUBLIC_SITE_URL, else the production domain on a Vercel production build or the branch address on a preview, else http://localhost:3000. The sitemap and the share card URLs are built on it.
toDeckPresentation(deck) The serializable slice the shell needs.
canSwitchColorMode(theme) / forcedColorMode(theme) What the color-mode toggle does for a given theme.
homeColorMode(theme) The mode a theme is shown in when only one can be: its defaultColorMode, or light for "system". The share card paints it.
cardFontWeights The weights a share card face can be set in, 100 to 900.
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.
resolveBackground(theme, background) Returns { variant, field? }: the name the canvas carries and the field it paints, with a role the theme does not paint in motion replaced by its fallback.
slideBackgroundModes The background variants the runtime paints for every deck.
slideBackgroundRoles The background roles, each mapped to the variant it renders as when the theme paints no field for it.
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, SlideThemeCard, SlideThemeCardColors, CardFontWeight, SlideColorMode, SlideHeaderMode, SlideFooterMode, SlideFooterModeInput, SlideLayoutMode, SlideBackgroundMode, BuiltInSlideBackgroundMode, PaintedSlideBackgroundMode, SlideBackgroundRole, ResolvedBackground, 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. Key it by a background role, hero, breaker, statement, or closing, so a deck that marks its slides by role shows the field under this theme and the role’s fallback under any other:

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 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. These 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 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.

Where no canvas mounts

The canvas mounts once the slide has read its URL, and never in a presenter preview, the page the console’s iframes load with presenterPreview=1. Until then, and in every preview, the background the theme painted underneath is the slide and the runtime is not fetched.

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
createSlideShareCard(deck) app/slides/[id]/opengraph-image.tsx Image, alt, contentType, generateStaticParams, size
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)
import { createSlideShareCard } from "@thebuilder/deckard-core/share-card"
import { deck } from "@/deck/deck"

const { Image, alt, contentType, generateStaticParams, size } =
  createSlideShareCard(deck)

export { alt, contentType, generateStaticParams, size }
export default Image

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 takes its origin from deckSiteUrl when you pass no siteUrl: NEXT_PUBLIC_SITE_URL, then the address Vercel gives the deployment, then http://localhost:3000. createSlideRoute’s generateMetadata sets metadataBase from the same function, so the share card each slide links resolves to an absolute URL on the same origin. 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.

createSlideShareCard

import { createSlideShareCard } from "@thebuilder/deckard-core/share-card"

function createSlideShareCard(deck: Deck): {
  Image: (props: { params: Promise<{ id: string }> }) => Promise<Response>
  alt: string
  contentType: string
  generateStaticParams: () => { id: string }[]
  size: { height: number; width: number }
}
Argument Type Default
deck Deck, the result of defineDeck required

Returns the exports of an opengraph-image.tsx route beside the slide page:

Export What it is
Image The default export. Renders one slide’s card with ImageResponse from next/og.
alt A slide from <deck title>, with by <brand> when header.brand differs from the title. Every card in the deck shares it.
contentType "image/png"
generateStaticParams Every slide id, the same list the slide page prerenders.
size { width: 1200, height: 630 }

Behaviour. next build prerenders one PNG per slide at <slides path>/<id>/opengraph-image, and each slide page links it as og:image and twitter:image, with twitter:card set to summary_large_image. The card paints the theme’s card.colors for its home mode, homeColorMode(theme): the brand and the slide position (3 / 18) along the top in muted, an accent rule, the slide title in foreground, and deck.description along the bottom in muted, or header.meta when the description is empty. The title steps down in size as it gets longer and clamps at three lines. It is set in card.font.family at card.font.weight, capitalised when card.font.uppercase is true; the eyebrow and footer use the same family at 400.

The face is fetched at build time from the Google Fonts CSS2 API, subset to the characters on the card and cached per family, weight, and text for the build. A theme with no card renders a grayscale card in the renderer’s default face and fetches nothing.

The route reads no request, so it prerenders with the slides. Re-export generateStaticParams from the image route as well: it is a route handler and does not take the slide page’s params, so without it no card prerenders.

Errors. A font fetch that fails, because the build is offline or Google Fonts does not carry the family, logs one warning naming the family and weight, and the card renders in the default face. The build does not fail. An id that names no slide answers 404. A theme card with a color that is not hex or rgb() fails defineDeck, naming the theme, the mode, and the field.

Example. The playground’s second deck lives at /example, so its card route sits beside that page:

import { createSlideShareCard } from "@thebuilder/deckard-core/share-card"
import { exampleDeck } from "@/deck/example-deck"

const { Image, alt, contentType, generateStaticParams, size } =
  createSlideShareCard(exampleDeck)

export { alt, contentType, generateStaticParams, size }
export default Image

Related. SlideThemeCard for the theme side, Share cards for the site URL and the font fetch, deckSiteUrl for the origin.

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?