# Theming and tokens

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from core/tokens/tokens.css. Do not edit: change the source and run the generator.

Two themes and three densities are token sets, not separate stylesheets (an optional theme may be one: `dist/themes/<name>.css`, linked after `plainkit.css` on purpose, never part of it): set `data-theme="dark"` or `data-theme="light"` (and optionally `data-density="compact"` or `data-density="spacious"`) on `<html>` or on any element to theme only that subtree. Every colour, size, space and shadow is a CSS custom property (307 declarations in the token stylesheet); use them, never literal colours. `dist/js/theme.js` exports `MAX_OVERRIDES`, `MAX_VALUE_LENGTH`, `buildOverrides`, `colourToHex`, `currentTheme`, `nameProblem`, `parseOverrides`, `parseTokenBlocks`, `sanitizeDict`, `sanitizeOverrides`, `setTheme`, `splitLength`, `toggleTheme`, `tokenKind`, `valueProblem`. To change a token, override it in a stylesheet you load after `plainkit.css` (an inline `style` attribute is blocked by the CSP; setting properties from script through `element.style.setProperty` is fine). `mountThemeEditor` (see `tools.md`) edits and exports these overrides.

```css
:root[data-theme="dark"] {
    --color-accent: #7c3aed;
}
:root[data-theme="light"] {
    --color-accent: #6d28d9;
}
```

## color tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--color-bg` | `#1e1e1e` | `#fafafa` |
| `--color-panel` | `#2a2a2a` | `#ffffff` |
| `--color-flyout` | `#363636` | `#ffffff` |
| `--color-input` | `#3a3a3a` | `#ffffff` |
| `--color-input-border` | `#505050` | `#d4d4d4` |
| `--color-surface` | `#3a3a3a` | `#ffffff` |
| `--color-surface-alt` | `#333333` | `#f1f5f9` |
| `--color-text` | `#e8e8e8` | `#1f2328` |
| `--color-muted` | `#a0a0a0` | `#6c747e` |
| `--color-muted-strong` | `#a8a8a8` | `#636b75` |
| `--color-accent` | `#4e93e3` | `#1d4ed8` |
| `--color-accent-hover` | `#5ca0f2` | `#1e40af` |
| `--color-accent-fill` | `#2276d7` | `#1d4ed8` |
| `--color-accent-fill-hover` | `#1f6fca` | `#1e40af` |
| `--color-border` | `#505050` | `#d9dde2` |
| `--color-link` | `#6aa9f5` | `#1d4ed8` |
| `--color-link-hover` | `#7cb5f7` | `#1e40af` |
| `--color-critical` | `#f87171` | `#b91c1c` |
| `--color-warning` | `#fbbf24` | `#92400e` |
| `--color-positive` | `#4ade80` | `#15803d` |
| `--color-notice-error` | `` | `var(--field-error)` |
| `--color-media-bg` | `#000` | (same) |
| `--color-required` | `#d97706` | (same) |

## space tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--space-1` | `0.25rem` | (same) |
| `--space-2` | `0.5rem` | (same) |
| `--space-3` | `0.75rem` | (same) |
| `--space-4` | `1rem` | (same) |
| `--space-5` | `1.25rem` | (same) |
| `--space-6` | `1.5rem` | (same) |
| `--space-8` | `2rem` | (same) |
| `--space-12` | `3rem` | (same) |
| `--space-2xs` | `var(--space-1)` | (same) |
| `--space-xs` | `var(--space-2)` | (same) |
| `--space-sm` | `var(--space-3)` | (same) |
| `--space-md` | `var(--space-4)` | (same) |
| `--space-lg` | `var(--space-5)` | (same) |
| `--space-xl` | `var(--space-6)` | (same) |
| `--space-2xl` | `var(--space-8)` | (same) |
| `--space-3xl` | `var(--space-12)` | (same) |

## text tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--text-meta` | `0.93rem` | (same) |
| `--text-read` | `1rem` | (same) |
| `--text-xs` | `var(--text-meta)` | (same) |
| `--text-sm` | `var(--text-meta)` | (same) |
| `--text-md` | `var(--text-read)` | (same) |
| `--text-base` | `var(--text-read)` | (same) |
| `--text-lg` | `1.1rem` | (same) |
| `--text-h1` | `2rem` | (same) |
| `--text-h2` | `1.5rem` | (same) |
| `--text-h3` | `1.17rem` | (same) |
| `--text-h4` | `var(--text-read)` | (same) |
| `--text-h5` | `var(--text-meta)` | (same) |
| `--text-h6` | `var(--text-meta)` | (same) |

## radius tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--radius-sm` | `4px` | (same) |
| `--radius-md` | `6px` | (same) |
| `--radius-lg` | `8px` | (same) |
| `--radius-xs` | `3px` | (same) |
| `--radius-xl` | `12px` | (same) |
| `--radius-pill` | `999px` | (same) |
| `--radius-round` | `50%` | (same) |

## shadow tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--shadow-card` | `0 1px 2px rgba(0, 0, 0, 0.45)` | `0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04)` |
| `--shadow-flyout` | `-4px 0 20px rgba(0, 0, 0, 0.4)` | (same) |
| `--shadow-modal` | `0 16px 48px rgba(0, 0, 0, 0.32)` | (same) |
| `--shadow-pop` | `0 4px 16px rgba(0, 0, 0, 0.45)` | (same) |

## font tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--font-sans` | `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif` | `` |
| `--font-heading` | `var(--font-sans, sans-serif)` | `` |
| `--font-mono` | `ui-monospace, Consolas, monospace` | `` |

## z-index tokens

| Token | Dark (or shared) | Light |
|---|---|---|
| `--z-sticky` | `10` | (same) |
| `--z-modal` | `1200` | (same) |
| `--z-flyout-backdrop` | `1500` | (same) |
| `--z-flyout` | `1501` | (same) |
| `--z-modal-over-flyout` | `1600` | (same) |
| `--z-pop` | `1700` | (same) |
| `--z-toast` | `1800` | (same) |
| `--z-tip` | `1900` | (same) |
| `--z-skip` | `2000` | (same) |

## Other tokens

Element and layout tokens (chart, code viewer, control heights, field states, ...). Names only; the values are in `dist/plainkit.css`.

`--app-footer-h` `--app-header-h` `--btn-mini-btn-warn-bg` `--btn-mini-btn-warn-hover-bg` `--btn-mini-fg` `--btn-primary-fg` `--btn-warn-bg` `--btn-warn-fg` `--btn-warn-hover-bg` `--card-error-border` `--chart-1` `--chart-2` `--chart-3` `--chart-4` `--chart-5` `--chart-6` `--chart-on` `--chart-track` `--chip-accent-bg` `--chip-accent-fg` `--chip-bg` `--chip-danger-bg` `--chip-danger-fg` `--chip-fg` `--chip-info-bg` `--chip-info-fg` `--chip-success-bg` `--chip-success-fg` `--chip-warn-bg` `--chip-warn-fg` `--content-lg` `--content-md` `--content-sm` `--csr-mark-bg` `--ctl-h` `--cv-attr` `--cv-bar` `--cv-bg` `--cv-border` `--cv-comment` `--cv-directive` `--cv-focus` `--cv-focus-edge` `--cv-gutter` `--cv-hover` `--cv-key` `--cv-keyword` `--cv-match` `--cv-muted` `--cv-number` `--cv-shadow` `--cv-string` `--cv-tag` `--cv-text` `--cv-type` `--cv-word` `--delta-down` `--delta-up` `--dg-card-label-fg` `--dg-clickable-hover-bg` `--dg-sortable-hover-fg` `--duration-base` `--duration-fast` `--duration-slow` `--ease` `--field-error` `--field-muted` `--field-ok` `--field-warn` `--flow-space` `--flyout-backdrop-bg` `--focus-ring` `--ft-selected-bg` `--gap-card` `--gap-control` `--gap-field` `--gap-heading` `--gap-label-input` `--gap-min` `--gap-section` `--inspector-w` `--modal-card-t-archived-bd` `--modal-card-t-archived-bg` `--modal-card-t-archived-fg` `--modal-card-t-product-bd` `--modal-card-t-product-bg` `--modal-card-t-product-fg` `--modal-overlay-bg` `--nav-item-min-h` `--notice-success` `--pad-card` `--pad-cell` `--pad-drawer` `--pad-footer` `--pad-form-section` `--pad-header` `--pad-modal` `--pad-nav-item` `--pad-page` `--pad-panel` `--page-crumbs-fg` `--pop-w` `--rating-on` `--shell-nav-w` `--shell-nav-w-collapsed` `--site-border-w` `--site-docs-nav-w` `--site-focus-offset` `--site-scroll-max-w` `--site-search-w` `--surface-blur` `--surface-border-highlight` `--surface-empty-offset` `--surface-mark-offset` `--surface-mark-w` `--surface-phone-w` `--surface-saturate` `--surface-tablet-w` `--surface-tint` `--toast-w` `--touch-target` `--tray-large-h` `--tray-large-w` `--tray-medium-h` `--tray-medium-w` `--tray-small-h` `--tray-small-w`

## Breakpoints

The page and the elements respond at named widths, desktop-first: a rule for a breakpoint applies at that width **and below**. Custom properties cannot be used inside `@media`, so the widths are resolved when the SDK is built (`core/tokens/breakpoints.json`); a prebuilt `dist` has these values, and other widths need a rebuild. The elements already respond on their own: do not restyle them at these widths.

| Name | Width | At or below | Above | Custom property | What changes there |
|---|---|---|---|---|---|
| `phone` | 640px | `(max-width: 640px)` | `(min-width: 641px)` | `--pk-bp-phone` | 67 elements, 193 rules |
| `tablet` | 1024px | `(max-width: 1024px)` | `(min-width: 1025px)` | `--pk-bp-tablet` | 11 elements, 41 rules |
| `wide` | 1280px | `(max-width: 1280px)` | `(min-width: 1281px)` | `--pk-bp-wide` | 2 elements, 4 rules |

The SDK's own element CSS writes these as `(--phone)` and `(--above-phone)`, which its build resolves; that syntax works only inside the SDK, not in your stylesheet.

In **your own** stylesheet write the literal query with the same width (`(max-width: 640px)`, `(max-width: 1024px)`, `(max-width: 1280px)`); a variable does not work in `@media`. In a script read the width from the page, never repeat the number: `import { mediaBelow } from './plainkit/js/breakpoints.js'` then `mediaBelow('phone').matches` (also `mediaAbove(name)`, `breakpoint(name)` for the number, `belowQuery(name)` and `aboveQuery(name)` for the query text). What each breakpoint changes, element by element, is in `dist/breakpoints.report.json`.
