---
title: Token contract
description: Every --slide-* custom property declared in @thebuilder/deckard-core/styles.css, its neutral default, and which part of the runtime reads it.
sidebar:
  label: Tokens
  icon: swatch-book
---

`@thebuilder/deckard-core/styles.css` declares every `--slide-*` token with a neutral default, inside `@layer base`. A theme redefines them on the canvas and wins, because it ships unlayered CSS and everything in the contract sits in the base layer.

The runtime depends on these names and never on a preset, so a deck with no theme still renders: neutral sizes, app surfaces, no decoration.

```css app/globals.css
@import "tailwindcss";
@import "@thebuilder/deckard-core/styles.css";
```

## Type

<SlideTokens column="Reads it" group="type" />

Every size is set against the 1920x1080 canvas at a 16px root, so `1rem` is 16 canvas pixels and the scale reads the way it does on a projector rather than the way it does in a browser window. Multiply a default above by 16 to get what it measures on a canvas 1080 pixels tall.

Line height rides along as a unitless multiplier in the blocks, so changing a size token keeps its leading proportional. Never pair a size token with a fixed `leading-7`.

## Spacing

<SlideTokens group="spacing" />

The frame those gutters leave is the whole slide, left aligned and uncapped: content starts at the left margin and runs to the right one. Blocks cap their own measure where prose needs it, and anything that wants to be centred centres itself.

## Figures

<SlideTokens group="figures" />

## Surfaces

<SlideTokens group="surfaces" />

## Backgrounds

`SlideBackground` renders one empty element carrying `data-slide-background`, and the theme paints each variant with these:

<SlideTokens group="backgrounds" />

There is no radial wash, veil, glow or spotlight token. The textures a theme has are flat colour, a rule, a grid, and a hatch, because a radial gradient reads as a smudge on a projector and as grey on paper.

The four `--slide-motion-*` tokens are read off the canvas by the animated background, and only for a variant the theme names in `motion`. See [Motion backgrounds](/reference/core#motion-backgrounds). A theme sets them per variant and per color mode the way it sets everything else it paints:

```css
.aurora-theme .slide-background[data-slide-background="hero"] {
  --slide-motion-color-1: #0a0d1f;
  --slide-motion-color-2: #3a2f86;
  --slide-motion-color-3: #1c7c96;
  background: linear-gradient(160deg, #0a0d1f, #1c2350);
}
```

That `background` is what the slide shows while the runtime loads, when the browser has no WebGL, and when the context is lost, so paint a variant you animate as though it never would.

## Media

<SlideTokens group="media" />

## Chrome

The colours, type, and spacing of the header and footer, plus the canvas each is allowed:

<SlideTokens group="chrome" />

A theme that redesigns the chrome to a different height sets `--slide-header-space` and `--slide-footer-space`, and the slide content moves with it.

## Published by the runtime

These are set in the DOM rather than declared in the contract, and a theme or a block reads them:

| Variable | Set on | What it carries |
| --- | --- | --- |
| `--slide-progress` | `[data-slide-progress]` | Position in the deck as a fraction from 0 to 1 |
| `--slide-chrome-top` | the canvas | `--slide-header-space` republished as an inset for a bleeding layout |
| `--slide-chrome-bottom` | the canvas | The same for the footer |
| `--deckard-scale` | the viewport | The measured fit, applied as a `scale()` transform |

A fullscreen block pads against the chrome insets so the deck header and slide counter never sit on top of a caption:

```tsx
className="pb-[calc(3rem+var(--slide-chrome-bottom,0px))]"
```

## Structural attributes

The runtime renders these and the theme styles them. They are as much a part of the contract as the tokens.

| Attribute | On |
| --- | --- |
| `data-slide-canvas` | The canvas element, which also carries the theme class |
| `data-slide-viewport` | The element that scales the canvas |
| `data-slide-frame` | The slide frame the overflow check measures |
| `data-slide-background` | The background element, valued with the variant name |
| `data-slide-motion` | The canvas inside it, valued with the field: `aurora`, `waves`, or `wash` |
| `data-slide-motion-state` | What that canvas is doing: `running`, `frozen`, or `unavailable` |
| `data-slide-header`, `data-slide-footer` | The chrome |
| `data-slide-header-brand`, `-title`, `-meta` | The three header parts |
| `data-slide-counter`, `-current`, `-separator`, `-total` | The counter |
| `data-slide-progress` | The progress element, absent entirely when `footer.progress` is `false` |
| `data-slide-surface` | A block that paints its own border or background |
| `data-slide-panel` | The `ContentSlideCard` panel |
| `data-slide-intro` | The eyebrow and heading block above a body |
| `data-slide-eyebrow` | The uppercase label above a title |
| `data-slide-title`, `data-slide-lead` | A slide's own headline and the sentence under it |
| `data-slide-kicker` | The label under a `FocusSlide` body |
| `data-slide-hero`, `data-slide-hero-meta` | The opener and its credit row |
| `data-slide-badge` | The pill above a centered opener's title |
| `data-slide-rail`, `-term`, `-detail` | The side column on `HeroSplitSlide` and `ProseSlide` |
| `data-slide-breaker`, `data-slide-breaker-index` | The section divider and the number it carries |
| `data-slide-accent-rule` | The short rule a minimal breaker or a statement opens on |
| `data-slide-statement`, `-text`, `-source` | The one-sentence layout, its sentence, its attribution |
| `data-slide-split`, `-main`, `-aside` | A `CodeSplitSlide` and its two sides |
| `data-slide-note`, `-index`, `-text` | One numbered note in that rail |
| `data-slide-prose`, `-body`, `-paragraph` | The label rail layout and its copy |
| `data-slide-quote`, `-text`, `-attribution`, `-portrait`, `-name`, `-source` | A pull quote, down to the portrait |
| `data-slide-list`, `-item`, `-marker`, `-text` | A `BulletList` and its three parts |
| `data-slide-reveal`, `-item`, `-item-accent`, `-title`, `-description` | A stepped `RevealList` and its parts |
| `data-slide-contents`, `-item`, `-index`, `-title`, `-folio` | A `ContentsList` row by row |
| `data-slide-columns`, `data-slide-column`, `-index`, `-label`, `-title`, `-text` | A `ColumnGrid` and one column of it |
| `data-slide-grid`, `data-slide-card`, `-accent`, `-label`, `-title`, `-body` | A `FeatureGrid` or `CardGrid` and its cards |
| `data-slide-media`, `-item`, `-frame`, `-overlay`, `-caption`, `-credit` | A media layout and its parts |
| `data-slide-table`, `-head`, `-heading`, `-row`, `-highlight`, `-cell` | A `DataTable` |
| `data-slide-timeline`, `-item`, `-marker`, `-done`, `-date`, `-label`, `-detail` | A `Timeline` and one milestone |
| `data-slide-log`, `-row`, `-tone`, `-time`, `-message`, `-status` | A `LogList`; `-tone` is valued `ok`, `note`, or `alert` |
| `data-stat-grid`, `-item`, `-value`, `-unit`, `-meter`, `-caption` | A `StatGrid`, down to the proportion bar |

These are the other half of the theme contract. Tokens are values; attributes are parts. A theme styles them, decorative `::before` and `::after` content included, which is how phosphor writes `[x]` where a list marker goes and ledger hangs a rule there instead, neither of them touching the block. A block may rewrite its markup and its class names freely as long as the attributes stay where they are.

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.

Code blocks are scoped too: `[data-slide-canvas] .shiki` has its shiki-painted background forced transparent, so a deck never restyles code the surrounding app renders.
