---
title: Blocks and the registry
description: 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.
sidebar:
  icon: blocks
---

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

```bash
deckard add block metrics
```

Or straight through shadcn:

```bash
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.

:::warning
`deckard init` writes `https://deckard.thebuilder.dk/r/{name}.json` into `components.json`, which is this documentation site. Nothing to run locally: `deckard add block` fetches from it directly. Point `--registry` at another URL with a `{name}` placeholder to install from your own.
:::

## Building it

From the repository root:

```bash
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](/slide-patterns) 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

**ContentSlideCard**

Eyebrow, heading, and lead above a bordered `--slide-surface` panel. For
body content with no surface of its own.

```tsx
<ContentSlideCard eyebrow="Context" description="Where this came from.">
  <p>Flat prose, a definition list, your own markup.</p>
</ContentSlideCard>
```

**OpenContentSlide**

The same intro with no panel, for content that brings its own frame.

```tsx
<OpenContentSlide eyebrow="Results" description="Three quarters in.">
  <FeatureGrid items={features} />
</OpenContentSlide>
```

**FocusSlide**

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.

```tsx
<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

```tsx
<ImageShowcaseSlide
  image={{
    src: diagram,
    alt: "System architecture",
    caption: "Everything upstream of the queue.",
    placeholder: "blur",
  }}
/>
```

```tsx
<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.
