Skip to content
Thanawi

Theming

How Thanawi tokens are layered, named, switched between modes, and exposed to Tailwind.

Token layers

Tokens come in three layers. Each layer may only reference the layer before it. Components use semantic tokens only. They never use primitives or raw values.

  1. 1

    Primitives

    The raw palette and scales. Context-free values in OKLCH.

    --th-teal-600
  2. 2

    Semantics

    Named by role. Values change per mode; names never do.

    --th-surface-primary
  3. 3

    Component usage

    Components consume semantic tokens through Tailwind utilities.

    bg-surface-primary

Naming

Every token has the --th- prefix. Semantic color tokens follow --th-<group>-<role>[-<state>]:

  • surface: backgrounds, from the page canvas (base) to filled controls (primary, negative).
  • content: text and icons. Use the on-* roles for content that sits on a filled surface.
  • border: dividers, outlines and the focus ring (focus).

Non-color tokens follow the same idea: --th-spacing-*, --th-font-size-*, --th-radius-*, --th-shadow-*, --th-duration-*, --th-ease-* and --th-z-*.

Modes

Light is the default and lives on :root. Dark mode is turned on with data-th-mode on any element, usually <html>. Every semantic name stays the same between modes; only the values change.

<html data-th-mode="dark">
  …
  <!-- a subtree can opt back into light -->
  <section data-th-mode="light">…</section>
</html>

The mode toggle in this site's header sets data-th-mode on <html> and remembers your choice.

Color format

OKLCH is the source of truth. Browsers with wide-gamut displays get P3-enhanced values, and browsers without oklch() get an sRGB hex fallback, which is declared first:

:root {
  --th-surface-primary: #017167;
}

@supports (color: oklch(0 0 0)) {
  :root {
    --th-surface-primary: oklch(0.493 0.087 184.2);
  }
}

@media (color-gamut: p3) {
  :root {
    --th-surface-primary: oklch(0.493 0.105 184.2); /* illustrative P3 value */
  }
}

Semantic colors

Every swatch below is painted from the live CSS variable, so it follows the mode toggle. Click a swatch to copy its var().

Surface

Content

Border

Tailwind bridge

@thanawi/tokens/tailwind.css maps tokens to Tailwind v4 theme variables with @theme inline. Utilities resolve to var(--th-*), so mode switching works without rebuilding.

UtilityResolves to
bg-surface-basevar(--th-surface-base)
text-content-mutedvar(--th-content-muted)
border-border-subtlevar(--th-border-subtle)
ring-border-focusvar(--th-border-focus)
rounded-mdvar(--th-radius-md)
shadow-smvar(--th-shadow-sm)
font-sansvar(--th-font-family-sans)
dark:[data-th-mode="dark"] subtree

Radius

--th-radius-none
--th-radius-xs
--th-radius-sm
--th-radius-md
--th-radius-lg
--th-radius-xl
--th-radius-2xl
--th-radius-full

Shadow

--th-shadow-none
--th-shadow-xs
--th-shadow-sm
--th-shadow-md
--th-shadow-lg
--th-shadow-xl