Guides

Theming · design tokens

179 CSS custom properties in three layers, BEM-named. Swap a stylesheet, you swap the brand. No Sass, no JS, no build.

The mental model · three layers

Rikiki's tokens are organised in three layers · two you author in a theme, and the component-scoped overrides you set on one host:

  1. Palette · raw colors with internal names (--rik-palette-mango-500, --rik-palette-night-900).Private. Components don't read them; you only touch them when writing a new theme.
  2. Semantic · intent-based names following BEM (--rik-surface-page, --rik-status-success__border,--rik-accent--strong). This is the public API.
  3. Component-scoped · --deck-<tag>-* overrides that default to the matching semantic token. Use them to retheme one instance without touching :root.

The three layers on one change

Take the cover's background. The palette holds the raw colour under a private name, the semantic layer gives it an intent, and the component reads that intent through a token of its own with the semantic value as fallback.

How the value resolves
/* LAYER 1 · palette · a raw value, private to the theme */
:root { --rik-palette-night-900: #06080f; }

/* LAYER 2 · semantic · the intent the components read */
:root { --rik-surface-inverse: var(--rik-palette-night-900); }

/* LAYER 3 · component · what deck-cover declares, fallback included
   (this line lives in the component, you do not write it) */
background: var(--deck-cover-bg, var(--rik-surface-inverse));

Each layer is a different reach. Change--rik-palette-night-900 and every surface built on it moves. Change --rik-surface-inverse and every inverse surface moves, whatever palette entry fed it. Change --deck-cover-bg on one host and only that cover moves · the difference the pair below shows.

One instance, one layer
<!-- One cover, on that same night background -->
<deck-cover><h1>Inherits the theme</h1></deck-cover>

<!-- The same cover, one value changed at layer 3 -->
<deck-cover style="--deck-cover-bg: #2d1b4e;"><h1>This one only</h1></deck-cover>
Live preview · slide canvas

The preview shows the overridden cover. Its sibling, untouched, would sit on#06080f instead · the value the palette holds and the semantic token forwards.

Swap a whole theme

One stylesheet, one swap. The semantic layer is identical across themes · only the palette values change underneath.

<head>
<!-- Default: tropical Rikiki -->
<link rel="stylesheet" href="rikiki/themes/rikiki.css">

<!-- Or: warm-paper Siliceum -->
<link rel="stylesheet" href="rikiki/themes/siliceum.css">

<!-- Or: your own -->
<link rel="stylesheet" href="themes/my-brand.css">

Two themes ship with the package:

  • rikiki · tropical-jungle palette on a deep navy. Unbounded + Inter (variable axes) + Space Mono via Google Fonts. Default.
  • siliceum · warm paper with a gold accent. Source Sans Pro + JetBrains Mono, self-hosted woff2.

Override one or two tokens

Keep the theme but tweak one semantic value. Set the token on :root · all components follow.

Inside your deck's <style>
:root {
  --rik-accent:           #ff0066;
  --rik-status-success:   #00aa44;
}

Override one component instance

Every component exposes its own --deck-<tag>-* tokens. Set them inline on the host and they cross the Shadow DOM boundary · regular CSS selectors don't, so this is the supported way to retheme one block.

One-off override
<deck-card style="
  --deck-card-bg: #1a0f2e;
  --deck-card-text: #fde9a3;
  --deck-card-radius: 4px;
">
  <h3>One-off retheming</h3>
  <p>Tokens cross the Shadow DOM boundary; CSS selectors don't.</p>
</deck-card>
Live preview

See the Component library for the per-component token list · every component declares its own--deck-<tag>-* family, and the catalogue entry names each one.

Naming convention

BEM with explicit prefixes:

  • --rik- · framework prefix (avoids collisions with consumer CSS)
  • __element · sub-part inside a block (__bg, __border, __text)
  • --modifier · variant or state (--soft, --strong, --hover, --faint)

No color name in the semantic layer. Names like --yellow, --orange, --green belong to the palette layer only. The semantic API is intent · the palette is implementation.

Reduced motion

Both shipped themes zero out the motion tokens underprefers-reduced-motion: reduce. The spring-ease curve (overshoot 1.8) is neutralised to a standard ease-out. Honouring prefers-reduced-motionis the technique behind WCAG 2.3.3, animation from interactions. If you author your own theme, copy this pattern:

themes/my-brand.css (excerpt)
@media (prefers-reduced-motion: reduce) {
  :root {
    --rik-motion-fast: 0ms;
    --rik-motion-base: 0ms;
    --rik-motion-slow: 0ms;
    --rik-motion__ease-spring: var(--rik-motion__ease-out);
  }
}

Contrast notes

The brand accent is a display colour, not a body-text colour. The ratios below are measured at build time from the shipped theme files against each theme's own --rik-surface-page, truncated rather than rounded up, and compared with the WCAG AA threshold for body text (4.5:1).

TokenThemeOn the page surfaceAA body text
--rik-accentrikiki2.81:1fails
--rik-linkrikiki4.49:1fails
--rik-linksiliceum4.57:1passes

That is why neither theme uses the raw accent for links: the default theme points --rik-link at --rik-accent--strong and siliceum declares a darker link colour of its own · the table says where each of them lands. If you write a custom theme, measure your--rik-link against your page background; the link CSS in Rikiki uses underlines, so non-text contrast is 3:1 · but body links read as text and should pass AA.

Type scale

Body sizes follow a modular ratio of 1.25 (major third)anchored at 1rem. The new t-shirt aliases (--rik-text-2xsthrough --rik-text-4xl) and the legacy--rik-font-size-* tokens point to the same values.

Note that deck-root sets the document font-size itself, so everyrem scales with the slide rather than with a fixed pixel value: on the default fixed canvas 1rem is 2.35 % of the canvas height (25.4px at the default canvas of 1080), and underfluid it is clamp(14px, 2.35vh, 42px). The injected rule targets adeck-root that is a direct child of body, so an embedded deck (the marketing site is one) leaves the host page'srem alone.

Write a third theme

Re-declare the palette, semantic surfaces, accent and fonts. Everything else inherits from defaults. Drop the file next to rikiki.css, point a <link> at it.

themes/my-brand.css
/* themes/my-brand.css · a minimal third theme.
   Re-declare only what you want to change · the rest cascades from
   whichever theme is loaded BEFORE this one. */

:root {
  /* ── LAYER 1 · palette (internal · never consumed by components) ── */
  --rik-palette-mango-500: #d97706;
  --rik-palette-mango-700: #92400e;
  --rik-palette-mango-100: rgba(217, 119, 6, 0.18);

  /* ── LAYER 2 · semantic (consumed everywhere) ── */
  --rik-surface-page:    #f6f4ed;
  --rik-surface-raised:  #ffffff;
  --rik-text-default:    #1a1810;
  --rik-text-default--muted:  #4a4540;
  --rik-text-default--faint:  #8a8580;
  --rik-border-default:  #e5e0d4;
  --rik-accent:          var(--rik-palette-mango-500);
  --rik-accent--strong:  var(--rik-palette-mango-700);
  --rik-accent--soft:    var(--rik-palette-mango-100);
  --rik-link:            var(--rik-accent--strong);

  /* Fonts · IBM Plex example */
  --rik-font-sans:       'IBM Plex Sans', system-ui, sans-serif;
  --rik-font-display:    'IBM Plex Sans Condensed', sans-serif;
  --rik-font-mono:       'IBM Plex Mono', ui-monospace, monospace;
}

Why tokens, not CSS selectors

Every Rikiki component lives in its own Shadow DOM. Regular CSS selectors don't cross the boundary · deck-cover h1{color: red} from your stylesheet has no effect. CSS custom properties DO inherit through the shadow boundary, which is why the entire override surface is tokens-only.

Token reference

The full semantic-layer surface, grouped by domain:

Semantic tokens
/* SEMANTIC LAYER · consumer-facing
 *  Naming · --rik-<block>__<element>--<modifier> (BEM).
 *  Components consume these · NOT --rik-palette-* (those are private). */

/* Surfaces */
--rik-surface-page  / --rik-surface-raised / --rik-surface-raised--strong
--rik-surface-sunken / --rik-surface-tint / --rik-surface-tint--strong
--rik-surface-inverse / --rik-surface-inverse--soft / --rik-surface-inverse__overlay

/* Text */
--rik-text-default / --rik-text-default--muted / --rik-text-default--faint
--rik-text-inverse / --rik-text-inverse--muted / --rik-text-inverse--faint / --rik-text-inverse--ghost

/* Borders */
--rik-border-default / --rik-border-default--subtle / --rik-border-inverse

/* Accent */
--rik-accent / --rik-accent--soft / --rik-accent--faint / --rik-accent--strong / --rik-accent__on

/* Status (success / danger / warn / info) */
--rik-status-success      / --rik-status-success__bg      / --rik-status-success__border
--rik-status-danger       / --rik-status-danger__bg       / --rik-status-danger__border
--rik-status-warn         / --rik-status-warn__bg         / --rik-status-warn__border
--rik-status-info         / __bg / __bg--mid / __bg--strong / __border / __text

/* Interactive */
--rik-interactive-bg / --rik-interactive-bg--hover / --rik-interactive-bg--active / --rik-interactive-bg--selected
--rik-interactive-fg / --rik-interactive-fg--hover

/* Focus / link / selection */
--rik-focus-ring / --rik-focus-ring--width / --rik-focus-ring--offset
--rik-link / --rik-link--hover / --rik-link--visited
--rik-selection__bg / --rik-selection__text

/* Decorative palette (when you need a specific hue) */
--rik-decor-orchid / --rik-decor-lime / --rik-decor-canary

/* Elevation, motion, z-index */
--rik-elevation-{1,2,3}
--rik-motion-{fast,base,slow} / --rik-motion__ease-{out,in-out,spring}
--rik-z-{base,sticky,overlay,modal,toast,tooltip}

/* Typography · modular scale 1.25 + legacy aliases */
--rik-font-sans / --rik-font-display / --rik-font-mono
--rik-text-{2xs,xs,sm,base,md,lg,xl,2xl,3xl,4xl}
--rik-font-size-{display,section,h1,h2,lead,body,sm,xs,mono,mono-sm,strong,big,mega,hook,stat}

/* Space / radius / icon */
--rik-space-{hair,2xs,1,2,3,4,5,6}
--rik-radius-{xs,sm,md,lg,pill}
--rik-icon-{xs,sm,md,lg,xl,2xl}

/* Slide chrome */
--rik-slide-padding-y / --rik-slide-padding-x / --rik-title-block

/* Code surface · used by deck-code */
--rik-code__bg / --rik-code__border / --rik-code__text
--rik-code__syntax-{keyword,string,number,comment,type,property,function}

Next