---
title: Tokens
description: The token primitive: a handful of semantic decisions, everything else derived, and the theming that falls out of it.
---

> LoamUI documentation, generated from the same source as the live page —
> treat it as authoritative for `@loamui/core`.

# Tokens

The first of LoamUI's three primitives: a handful of semantic custom properties that every component reads. A small palette of colours and neutrals, plus fluid scales: everything else is derived by recipe, so theming is overriding `--loam-*` values at any scope. No `ThemeProvider`, no JavaScript.

## The surface

Small enough to read in full. A component library that mints thousands of tokens makes every one
a decision nobody can audit; here the decisions are the hues and neutrals, and the rest is
arithmetic:

## Rebrand in one line

One token carries the brand: `--loam-color-primary`. Everything else (the soft tint, hover, active,
solid fills, focus rings) is _derived_ from it by recipe, so there is nothing else to keep in
sync:

```css
:root {
  --loam-color-primary: light-dark(oklch(0.62 0.2 275), oklch(0.72 0.17 275)); /* violet */
}
```

The brand colour appears wherever the design says primary: focus rings, checked states, carets,
text selection, and `primary` context regions. (Buttons are neutral by default; the demo below is
wrapped in a primary region so you can see the change.) Scope the token to a subtree to theme
just part of a page: the nearest declaration up the tree wins, so both cards below run identical
CSS and differ only in where the token is set:

```tsx

  "--loam-color-primary": "light-dark(oklch(0.62 0.2 275), oklch(0.72 0.17 275))",
  "--loam-context": "primary",
}}>
</div>
```

  </div>
</div>

## Dark mode

Dark mode is native. Tokens are defined with CSS `light-dark()` and the root declares
`color-scheme: light dark`, so the user's OS preference is followed with no JavaScript and no
configuration; that is the default state.

The stylesheet can only speak once it has loaded. Add the matching meta tag so the browser paints
the canvas in the right scheme _before_ CSS arrives (otherwise dark-preference users get a flash
of light canvas on every load):

```html
```

To override the preference, set `data-theme="dark"` or `data-theme="light"`: on the root for the
whole app, or on any element for just that subtree (the attribute simply sets `color-scheme`, so
every `light-dark()` token re-resolves there). Remove the attribute to follow the OS preference
again.

The same mechanism gives you an inverted "on-dark" section: set `data-theme="dark"` on the region
(or `color-scheme: dark` in its CSS; the attribute is just a setter for it) and every
`light-dark()` token flips. One caveat: colours already resolved on an ancestor inherit as
resolved values and don't re-resolve, so the inverted region must also re-declare `color` (e.g.
`color: var(--loam-color-fg)`) for descendants to pick up the flipped value.

> This is one instance of LoamUI's baseline posture: **the user's stated preferences are the default.** Colour scheme is followed natively, motion exists only inside `prefers-reduced-motion: no-preference`, and forced colour palettes are honoured rather than overridden. Everything beyond that baseline (a saved theme, an animation) is an explicit opt-in layered on top.

## Contexts

A **context** declares what a region _means_, as a custom property (`--loam-context`) that every
LoamUI component inside adopts. The mechanics (the vocabulary, one-element regions, why the
property lives on an ancestor) are the [Contextualism guide](/docs/contextualism)'s subject;
what matters for theming is that a context remaps _semantic colour tokens only_, so it composes
with everything on this page:

</div>

```css
/* the idiomatic form: a named region declares its meaning in
   its own stylesheet (a style attribute works for one-offs) */
.danger-zone {
  --loam-context: danger;
}
```

```tsx
```

> Theme, context, and instance are one mechanism at three scopes: remap tokens on `:root` to set a brand, declare a context on a region to give it meaning, set a property on an instance to override one control.

## Most useful to override

Start here when theming:

- `--loam-color-primary`: the brand colour (the soft tint, solid fill, focus ring and hover/active
  states all derive; there is nothing else to sync)
- `--loam-color-bg`, `--loam-color-surface`, `--loam-color-fg`, `--loam-color-line`: surfaces & text
- `--loam-radius-md`, `--loam-radius-lg`: corner rounding
- `--loam-font`, `--loam-font-display`: the body and heading font families (both `system-ui` by
  default; set either and it flows through the whole stack)
- `--loam-duration-sm/md/lg`, `--loam-ease`: motion by intent (micro feedback, defaults, overlay
  enter/exit)

## Fluid type & spacing

The type (`--loam-text-xs`…`3xl`) and spacing (`--loam-space-xs`…`xl`) scales are fluid `clamp()`
values in container units (`cqi`), generated with [Utopia](https://utopia.fyi); the calculator
parameters are committed as comments in `tokens.css`. Without a container they respond to the
viewport; declare one on any region to make its LoamUI typography respond to _that region's_
width instead:

```css
.sidebar {
  container-type: inline-size; /* LoamUI text in here now scales to the sidebar */
}
```

Corner radii are deliberately _not_ fluid: rounding shouldn't breathe. Control heights aren't
tokens at all: buttons and form controls share one derived anatomy (padding + line-height +
border), so they align by construction at every container width.

> Because tokens cascade, you can theme per-brand or per-section by setting variables on any wrapper element; the whole theme is just values in the cascade.

## Extending & overriding styles

Tokens cover most theming. When you need to change something a token doesn't expose, LoamUI gives
you three escape hatches, and none of them need `!important`.

### 1. Target the class names

Every component's scope root has a stable, prefixed class: `.loam-Button`, `.loam-Card`,
`.loam-Input-field`, and so on. The parts inside are plain elements and short classes, shown in
each component's CSS tab. Because LoamUI's styles live inside a CSS `@layer`, any rule you write
_outside_ a layer automatically beats them; you never fight specificity:

```css
/* Unlayered CSS always wins over LoamUI's layered CSS: no !important */
.loam-Button {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.loam-Card {
  box-shadow: 0 10px 40px -12px rgb(0 0 0 / 0.25);
}
```

### 2. Add your own className

Every component forwards `className` and `style` (and all native DOM props) to its root element,
so you can scope overrides to specific instances:

```tsx
```

```css
.cta {
  border-radius: 999px;
  padding-inline: 2rem;
}
```

### 3. Order your own layer

Prefer explicit control over the cascade? Declare a layer _after_ LoamUI's and put your overrides
there; they win by layer order, no matter the selector specificity:

```css
/* Declare the full order once: correct wherever it appears,
   before or after importing LoamUI's stylesheet. */
@layer loamui.tokens, loamui.elements, loamui.components, app;

@layer app {
  .loam-Tabs .tab {
    font-weight: 600;
  }
}
```

> The class names are a stable, documented API; the exact selectors for every component are in its real stylesheet, under the **CSS** tab on its docs page.
