Skip to content

Themes ​

theme.css decides how a deck looks. Slides supply structure and class names; the theme supplies the shared look, and a slide's own slides/<id>.css adds decoration that only that slide uses. This page covers the token contract, how to grow a theme's vocabulary, and why each deck owns a copy.

Thirteen tokens ​

If class names are the contract between HTML and theme, custom properties are the surface where appearance is swapped. Every theme publishes these thirteen tokens on .slide. They live on .slide, not :root, so they never leak into the presenter chrome. That is the same reason top-level selectors are forbidden (DEK012). The view transition sits outside every slide, so dek hands it the tokens set on .slide as well; see View Transitions.

TokenRole
--fg --bg --accent --mutedColor
--font-title --font-bodyType
--size-title --size-body --size-captionSize
--gap --padSpace
--radiusCorners
--step-transitionMotion

The color tokens are all meant for text. In the bundled theme, --fg, --muted, and --accent each reach 4.5:1 against --bg, so any of them passes DEK031 as body text. Keep that true when you change them.

Raw colors, font-family values, and absolute units may appear only when assigning a --* property. Everywhere else, use var() or calc(var() …). Unitless 0, thin, and em are allowed. A var() with a raw fallback, such as var(--fg, #fff), counts as a raw value. A missing token is DEK015; a raw value outside a token is DEK014. You may add as many deck-specific tokens as you like.

Every selector must sit under .slide. ::view-transition-* pseudo-elements and at-rules such as @keyframes and @media are the only exceptions.

Growing the vocabulary ​

A class the theme does not define is DEK010. You clear it by adding the class to theme.css. That is intentional: extending the vocabulary is a design decision, and lint should not forbid it. Lint's job is to make the decision cost one deliberate step and to leave a reviewable diff. DEK013 caps the total class count (40 by default) so that step is not repeated forever.

New classes land in the deck's own theme.css, so no other deck is affected. When a class earns its place, lift it into the project theme with cp.

Not every class deserves that step. Decoration that one slide uses goes in that slide's own stylesheet instead, where it neither counts toward DEK013 nor leaks into other slides. See Slide stylesheets.

Layout examples ​

A layout is a promise about markup: split expects a parent node and two results, tree a parent and a stack. Write that promise down as a comment directly above the layout's first rule, and it travels with the theme.

css
/* @layout split
<section class="slide" data-layout="split">
  <h2 class="slide-title">One source, two results</h2>
  <p class="node node-parent">Source</p>
  <p class="node" data-step="1">Result</p>
  <p class="node" data-step="2">Result</p>
</section>
*/
.slide[data-layout="split"] {

dekc theme split prints the example, and dekc theme lists which layouts have one. The bundled theme and the sample's themes carry an example for every layout, so an agent can start a slide from the markup the layout was designed for instead of guessing.

Slide numbers ​

Every slide carries its place in script.md: --dek-slide-number counts from 1, and --dek-slide-count is how many slides the deck has. The build, the dev server, shots, and the PDF all set them the same way, so a folio printed from them follows the script whenever a slide is added, removed, or moved with dekc mv. Nothing in the slide's HTML holds a number.

Pass them to a CSS counter to print them in any counter style:

css
.slide {
  counter-reset: folio var(--dek-slide-number) folios var(--dek-slide-count);
}

.slide::after {
  content: counter(folio, decimal-leading-zero) " / " counter(folios);
}

Which slides show a folio is the theme's call, as it is in a book: hide it on the cover with .slide[data-layout="cover"]::after { content: none; }, and the cover still counts as slide 1.

Copy, then freeze ​

The project's theme.css is the starting point for new decks. Edits flow in one direction: down into a new deck on creation, and back up by hand when you decide something is worth keeping.

bash
dekc new 2026-09-dek
dekc new 2026-09-dek --theme-from 2026-04-vite
cp decks/2026-09-dek/theme.css theme.css

The reasoning is in Projects and Decks.

Next ​

Turn conventions into a loop you cannot forget: Lint.

Released under the MIT License.