---
title: Themes
description: The built-in presets, how a deck imports one, what ejecting gives you, and what it takes to write your own.
sidebar:
  icon: palette
---

A theme owns every audience-facing color, size, and background in the deck, including the header and the footer. Its stylesheet is scoped to a class that `SlideCanvas` puts on the canvas element, so the deck controls, command center, and presenter console keep the app tokens and stay readable whatever the deck looks like.

A theme is three files:

| File | What it holds |
| --- | --- |
| `theme.css` | Every token and every selector, scoped to the theme class. |
| `index.ts` | One `SlideTheme` object. |
| `THEME.md` | What each token controls and what this theme sets it to. |

## The built-ins

<ThemeStrip />

Each frame is a slide canvas, not a picture of one, painted by that theme's own stylesheet in the color mode the theme opens on. Follow one into the [theme gallery](/themes) for the same frames at a size you can read, with a light and dark switch on every card and the palette behind it.

<ThemeTable />

## Picking one

Import it and hand it to `defineDeck`. Importing the module pulls in its stylesheet:

```ts deck/deck.ts
import { defineDeck } from "@thebuilder/deckard-core"
import { phosphor } from "@thebuilder/deckard-themes"
import { slides } from "@/deck/slides"

export const deck = defineDeck({ slides, theme: phosphor, /* ... */ })
```

`deckard add theme <name>` makes that edit for you, rewriting both the import and the `theme` property.

Only the theme a deck imports reaches the bundle. The barrel re-exports every built-in and the bundler drops the modules nothing uses, stylesheets included.

### The stylesheet on its own

`@thebuilder/deckard-themes/<name>/theme.css` is the same file the module imports, exported as a subpath so something that is not a deck can load it:

```ts
import "@thebuilder/deckard-themes/ledger/theme.css"
```

A deck never needs this. It exists for a page that renders a slide outside the runtime, such as the [theme gallery](/themes) on this site, which builds a scaled-down canvas out of the real markup and lets each theme's own stylesheet paint it.

## Ejecting

`deckard eject theme` copies the theme the deck imports out of the installed package into `deck/theme/` as `theme.css`, `index.ts`, and `THEME.md`, then repoints `deck/deck.ts` at the copy. From then on the theme is your source and the package never touches it.

```bash
deckard eject theme
deckard eject theme --theme ledger   # eject a different built-in instead
```

It refuses when `deck/theme/` already exists.

:::warning
An ejected theme no longer receives updates from `@thebuilder/deckard-themes`. If the built-in you started from gains a token or fixes a contrast bug, your copy does not, and no check compares the two files.
:::

## The SlideTheme object

```ts deck/theme/index.ts
import type { SlideTheme } from "@thebuilder/deckard-core"

import "./theme.css"

export const theme = {
  className: "my-theme",
  colorModes: ["light", "dark"],
  defaultColorMode: "system",
  id: "my-theme",
} satisfies SlideTheme
```

| Field | Type | What it does |
| --- | --- | --- |
| `className` | `string` | The class `SlideCanvas` puts on the canvas. Every selector in `theme.css` is scoped to it. |
| `colorModes` | `("light" \| "dark")[]` | Which modes the stylesheet actually carries. |
| `defaultColorMode` | `"light" \| "dark" \| "system"` | What a browser opens on. |
| `id` | `string` | Names the theme in `deckard validate` and `deckard eject theme`. |

`defineDeck` validates this. `defaultColorMode: "system"` needs both modes in `colorModes`. A theme listing one mode pins the canvas to it and hides the light/dark toggle.

## The token contract

`@thebuilder/deckard-core/styles.css` declares every `--slide-*` token with a neutral default, in the `base` layer. A theme redefines them on the canvas and wins, because it ships unlayered CSS. The runtime depends on the tokens, never on a preset, so a deck with no theme still renders in neutral sizes with no decoration.

The [token reference](/reference/tokens) lists all of them. The short version of what a theme sets:

- **Type**: the font families, the size scale, and the label tracking.
- **Surfaces**: the two radii, a surface color, a muted surface, a border, a shadow.
- **Backgrounds**: a grid color and pitch, a rule color, and the `--slide-scanline`, `--slide-hatch` and `--slide-halo` effects, which the `background` variants paint with.
- **Chrome**: the colours and type of the header and footer, plus `--slide-header-space` and `--slide-footer-space` for how much canvas each gets.
- **Media**: a foreground pair and the overlay gradients for text over photography.
- **Figures**: `--slide-figure-size`, its unit size, and the height of a `StatGrid` meter.

## The other half of the contract

Tokens are values. Data attributes are parts, and they are the rest of the contract.

Every part of a block a theme might want to reach carries one, from `data-slide-eyebrow` and `data-slide-list-marker` to `data-stat-meter`. A theme styles those attributes, decorative `::before` and `::after` content included, which is how phosphor writes `[x]` where a list number goes and ledger hangs a rule there instead, neither of them editing the block. The [full list](/reference/tokens) is also the "Block part contract" comment at the bottom of `@thebuilder/deckard-core/styles.css`, and a block may rewrite its markup and its class names freely as long as those attributes stay put.

A theme styles parts. It does not add content. A boot log, a status table, a cursor line with words in it are slide content, so they belong in a block the deck composes, however much they look like decoration.

## Background variants

A slide picks one with `background`:

| Value | What the theme paints |
| --- | --- |
| `"default"` | The theme's standard slide background. |
| `"spotlight"` | A second texture, usually a hatch, behind an opener. |
| `"grid"` | A ruled or dotted field. |
| `"accent"` | The inverted statement slide. |
| `"none"` | Nothing painted. |

A theme may name variants of its own. A deck writes the name as `background`, the theme styles `[data-slide-background="<name>"]`, and `deckard validate` reports a name neither the runtime nor the theme paints. A theme paints one of its own variants with an animated canvas instead of CSS by naming it in `motion`, which is in the [core reference](/reference/core#motion-backgrounds).

`SlideBackground` renders one element carrying `data-slide-background`, and the canvas carries the variant name too, so a theme can restyle the chrome along with the sheet. What each variant paints lives entirely in `theme.css`, so changing the look is a theme edit. Read the theme's `THEME.md` first, and run `deckard eject theme` if it is still an import.

`"accent"` floods the canvas with the theme accent and flips the ink, chrome included. Use it once or twice in a deck, for the one sentence that has to land, and never on two slides in a row.

No variant paints a radial wash or a corner glow. The textures a theme has are flat colour, a rule, a grid, and a diagonal hatch.

## Writing your own

Start by ejecting the built-in closest to what you want. Writing `theme.css` from scratch means rediscovering which tokens the blocks actually read, and the `THEME.md` that comes with an ejected theme is that list.

Then:

1. **Rename the class and the id**

    Change `className` and `id` in `index.ts`, and the selector prefix
    throughout `theme.css`. They do not have to match; every built-in uses
    `<id>-theme` as the class.

2. **Set the tokens**

    Work top-down: the shadcn color variables first (`--background`, `--card`,
    `--primary`, `--border`, `--foreground`, `--muted`), then the `--slide-*`
    layer on top. The blocks read the slide tokens, so most visual changes are
    one line.

3. **Do both color modes**

    Or declare only one in `colorModes` and pin the deck to it. `deckard
    validate` checks that the dark block defines nothing the light block does
    not, and that `colorModes` matches the blocks the stylesheet carries.

4. **Style the chrome**

    The header and footer are yours. Core renders `[data-slide-header]` and
    `[data-slide-footer]` with named parts for the brand, title, meta line,
    counter, and progress element. See [Deck chrome](/reference/deck-config#deck-chrome).

5. **Validate**

    ```bash
    deckard validate
    ```

    It checks the theme class is a real selector in the stylesheet the deck
    renders, and names which file it read.

Style inside the canvas with semantic tokens (`bg-card`, `text-muted-foreground`) or slide tokens. Never a hardcoded color, and never a raw font size where a token exists.
