Guides

Theming · design tokens

~140 CSS custom properties, two layers, BEM-named. Swap a stylesheet, you swap the brand. No Sass, no JS, no build.

The mental model · two layers

Rikiki's tokens are organised in two layers (three if you count component-scoped overrides):

  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.

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>

See the Component library for the per-component token list (every component declares its own --deck-<tag>-* family · 9–12 tokens per component on average).

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 under prefers-reduced-motion: reduce. The spring-ease curve (overshoot 1.8) is neutralised to a standard ease-out. WCAG 2.3.3 compliance, vestibular safety. 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 #f07020 on paper is 3.2:1, which fails WCAG AA for body text. Both themes route --rik-link through --rik-accent--strong (a darker shade that passes 4.5:1). If you write a custom theme, audit 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-2xs through --rik-text-4xl) and the legacy --rik-font-size-* tokens point to the same values.

Note that the slide root applies html { font-size: clamp(14px, 2.35vh, 42px) } so every rem scales with the viewport height. That's a deliberate choice for slides; if you embed Rikiki in a regular page (the marketing site does), drop that rule from your own stylesheet.

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.

Concretely: every component declares its custom property reads with the semantic fallback, e.g. background: var(--deck-cover-bg, var(--rik-surface-inverse)). Override --deck-cover-bg for one instance, override --rik-surface-inverse for all of them.

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}