Blocks and the registry
Slide blocks install into your app as source through a shadcn registry. What the items contain, how to install them, and where they are served from today.
Slide blocks are not in @thebuilder/deckard-core. They install into your app as source files through shadcn, into app/slides/blocks/, and you edit them from then on.
Expect to edit them. A block that assumed your theme would be a block you fight. HeroSlide caps its headline with text-balance tuned for a sans display face; on a serif you will want to change it, and you can, because it is your file.
The items
| Item | Installs | Contains |
|---|---|---|
block-typography |
typography.tsx |
Eyebrow, SlideHeading. Every other block depends on it. |
block-slide-layouts |
templates.tsx |
HeroSlide, HeroSplitSlide, HeroCenteredSlide, BreakerSlide, MinimalBreakerSlide, StatementSlide, CodeSplitSlide, ContentSlideCard, OpenContentSlide, FocusSlide. |
block-collections |
collections.tsx |
RevealList, BulletList, ContentsList, ColumnGrid, FeatureGrid, CardGrid. |
block-prose |
prose.tsx |
QuoteSlide, ProseSlide. |
block-tables |
tables.tsx |
DataTable, Timeline, LogList. |
block-media |
media.tsx |
ImageShowcaseSlide, MediaPair, MediaGallery, FullscreenMediaSlide. |
block-metrics |
metrics.tsx |
StatGrid. |
preset-blocks |
all of them | Plus the one stylesheet line a consumer cannot guess. |
preset-blocks is the one to run. Alongside the block files it adds @import "@thebuilder/deckard-core/styles.css" to your app stylesheet, which carries the token contract and registers the package’s own compiled output as a Tailwind source. That import is the entire build wiring. There is no transpilePackages entry and no @source line for you to add.
Every block is left aligned and fills the padded frame. The source designs put content at the left margin and run it to the right one, and only HeroCenteredSlide and MinimalBreakerSlide centre anything. ProseSlide caps its measure, in canvas pixels rather than ch, because the canvas is 1920 wide and never changes.
Themes used to be registry items and are not any more. They ship as @thebuilder/deckard-themes, so a deck gets one by importing it rather than by installing files it did not ask to own.
Installing
deckard add block metrics
Or straight through shadcn:
pnpm dlx shadcn@latest add @deckard/preset-blocks
deckard add block reads the registry URL from components.json under registries["@deckard"], or takes --registry <url> with a {name} placeholder. It probes the host before doing anything and tells you what to start when the probe fails. --yes passes shadcn -y -o, overwriting the files it installs without asking.
shadcn is a dependency of @thebuilder/deckard-cli, pinned to the version the registry is built with, and deckard add runs that installed binary. There is no dlx and no download, so the same version runs on every package manager.
Building it
From the repository root:
pnpm registry:build
That runs shadcn build --output apps/docs/public/r, writing one JSON file per item. This site serves public/ as static files, so each item lands at /r/<name>.json.
The source of truth is registry.json at the repository root. Block files point at apps/playground/app/slides/blocks/; the preset barrel points at the matching CLI template source. deckard validate --registry <path> checks that every source path exists.
Composing
Start with the slide patterns gallery when you know the job a slide has to do but not the component name. This page documents how the source files install and how their APIs compose.
Two rules cover almost every slide.
One surface per slide. A framed panel holds flat content, an open frame holds content that brings its own border. Blocks with a surface carry data-slide-surface; ContentSlideCard’s panel carries data-slide-panel and always paints its card. Nest them and you get a frame inside a frame, plus a development console warning naming OpenContentSlide and FocusSlide.
Prefer an explicit variant component over a boolean prop. ContentSlideCard takes flat content, OpenContentSlide takes content that brings its own border, and FocusSlide hands one block the whole frame.
The layout blocks
Eyebrow, heading, and lead above a bordered --slide-surface panel. For
body content with no surface of its own.
<ContentSlideCard eyebrow="Context" description="Where this came from.">
<p>Flat prose, a definition list, your own markup.</p>
</ContentSlideCard>The same intro with no panel, for content that brings its own frame.
<OpenContentSlide eyebrow="Results" description="Three quarters in.">
<FeatureGrid items={features} />
</OpenContentSlide>One block, with no heading, lead, or panel. It hands the block the whole
frame at the normal type scale, so a code sample shows more lines rather
than bigger ones, and takes one optional kicker for orientation.
<FocusSlide kicker="deck/slides.tsx">
<CodeBlock code={sample} language="typescript" />
</FocusSlide>All of HeroSlide, BreakerSlide, ContentSlideCard, and OpenContentSlide fall back to the slide’s own title through useSlideTitle() when you do not pass one, so a title stays defined in one place.
Media
<ImageShowcaseSlide
image={{
src: diagram,
alt: "System architecture",
caption: "Everything upstream of the queue.",
placeholder: "blur",
}}
/>
<FullscreenMediaSlide
media={{ kind: "video", src: "/videos/launch.mp4", autoplay: true }}
overlay="strong"
>
<h2>Launch</h2>
</FullscreenMediaSlide>
FullscreenMediaSlide takes variant ("background", the default, bleeds to every edge; "framed" insets it in a bordered surface) and overlay ("none", "subtle", "medium" by default, "strong") for text readability over media. Both blocks take next/image sources, forward blurDataURL from a static import, and accept unoptimized so an SVG served from public/ skips the image optimizer instead of needing images.dangerouslyAllowSVG. Video goes through SlideMediaVideo from @thebuilder/deckard-core/components, which suppresses autoplay inside the presenter preview iframe, and defaults an autoplaying video to muted and looping with no controls.
Both pad against --slide-chrome-top and --slide-chrome-bottom, so the deck header and slide counter never sit on top of a caption. Use them instead of dropping an <img> on a slide, which will not fit the canvas and will not respect the chrome.
Known edges
FeatureGrid renders three columns. Four items give you three on one row and one alone on the next; reach for CardGrid with columns={2} instead.
MediaPair takes a tuple of exactly two panels, so a third is a compile error. Two frames side by side is the layout; three is MediaGallery, which puts them on a grid rather than squeezing the row until none of them reads.
DataTable caps nothing. How many rows fit depends on the theme’s type scale, so deck:check-overflow is the thing that tells you, not the type. Four or five rows and four columns is what the source templates show.
CardGrid marks one card with accent, which takes --slide-accent-soft. Marking two marks neither.
StatGrid takes two to four stats, typed as a union of tuples, so a fifth is a compile error. At five, reach for DataTable. Each stat takes an optional unit suffix and an optional meter, a proportion from 0 to 1 that draws a bar under the figure. Leave the meter out unless you can say what it is a proportion of. Captions are capped at a 26-character measure, sized for “up from 2.6k” and not for a sentence, so give the figure something to be measured against.