# Tokens

> Every --slean-* token with its default value and purpose, rendered from the tokens file and grouped by kind.

Page: https://svelte-lean.vercel.app/styling/tokens · Markdown: https://svelte-lean.vercel.app/styling/tokens.md · Index: https://svelte-lean.vercel.app/llms.txt

Every --slean-\* custom property of @svelte-lean/styles, generated from tokens.css at build time.

## Definition

- Custom properties on `:root` in the `slean.tokens` layer, in `oklch`; component files define none.
- Semantic tokens (`--slean-bg`, `--slean-fg`, `--slean-border`) read the neutral scale.
- Swatches show package defaults; this site's controls use its own palette ([Themes](https://svelte-lean.vercel.app/styling/themes.md)).

`app.css, an unlayered override`

```css
/* A theme is a set of token values. Redefine them on :root to retheme every primitive. */
:root {
	--slean-primary-main: oklch(45% 0.13 150);
	--slean-primary-dark: oklch(40% 0.13 150);
	--slean-radius: 0.25rem;
	--slean-font-sans: 'IBM Plex Sans', system-ui, sans-serif;
}
```

## Spacing

one unit and its multiples. Override --slean-spacing on :root to scale every gap and padding of the package at once.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-spacing` | `0.25rem` | same as light |
| `--slean-space-1` | `calc(var(--slean-spacing) * 1)` | same as light |
| `--slean-space-2` | `calc(var(--slean-spacing) * 2)` | same as light |
| `--slean-space-3` | `calc(var(--slean-spacing) * 3)` | same as light |
| `--slean-space-4` | `calc(var(--slean-spacing) * 4)` | same as light |
| `--slean-space-5` | `calc(var(--slean-spacing) * 5)` | same as light |
| `--slean-space-6` | `calc(var(--slean-spacing) * 6)` | same as light |
| `--slean-space-7` | `calc(var(--slean-spacing) * 8)` | same as light |
| `--slean-space-8` | `calc(var(--slean-spacing) * 10)` | same as light |

## Shape

one radius and its steps. --slean-radius is the medium step (fields, buttons, cards); 0 gives a square theme.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-radius` | `0.625rem` | same as light |
| `--slean-radius-sm` | `calc(var(--slean-radius) * 0.6)` | same as light |
| `--slean-radius-md` | `var(--slean-radius)` | same as light |
| `--slean-radius-lg` | `calc(var(--slean-radius) * 1.4)` | same as light |
| `--slean-radius-full` | `9999px` | same as light |

## The space between a dialog's children (none by default

the header and the footer bring their own). Set it here rather than with gap on the dialog, so the close button, which takes no room, cancels it.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-dialog-gap` | `0px` | same as light |

## Control heights of data-size="small" (32px)

"medium" (36px, the default of every form control, button and bar) and "large" (44px); xs (28px) is for dense rows. A coarse pointer raises them to 40, 44 and 48px; data-density changes them for a subtree (block 4).

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-control-height-xs` | `1.75rem` | same as light |
| `--slean-control-height-sm` | `2rem` | same as light |
| `--slean-control-height-md` | `2.25rem` | same as light |
| `--slean-control-height-lg` | `2.75rem` | same as light |

## The border of a field under the pointer (outlined variant)

Unset by default, so it is the field's current color (data-color, primary by default); a theme sets it to soften the hover, for example to var(--slean-fg-muted).

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-control-border-hover` | `initial` | same as light |

## The check mark at the end of a chosen option row

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-option-check-size` | `1rem` | same as light |

## The side of a file drop tile (data-variant="picture-card" and "picture-circle")

each chosen file and the add tile.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-file-drop-tile-size` | `6.5rem` | same as light |

## Icons (ADR 0007)

16×16 SVG masks. A primitive paints one with \`background: currentColor; mask: var(--slean-icon-…) center / contain no-repeat\`, so it takes the text color and follows the theme. Replace a token to swap an icon set.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-icon-select` | `var(--slean-icon-chevron-down)` | same as light |

## How far the select icon turns while the list is open

a chevron points up; an up-and-down pair stays as it is (0deg).

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-icon-select-turn` | `180deg` | same as light |
| `--slean-icon-chevron-down` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-chevron-up` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 10l4-4 4 4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-chevron-left` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10 4L6 8l4 4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-chevron-right` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4l4 4-4 4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-chevrons-left` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 4L4 8l4 4M12 4L8 8l4 4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-chevrons-right` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 4l4 4-4 4M8 4l4 4-4 4' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-check` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-minus` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8h9' stroke='black' stroke-width='1.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-plus` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 3.5v9M3.5 8h9' stroke='black' stroke-width='1.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-close` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 4.5l7 7m0-7l-7 7' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-clear` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='16' height='16' fill='white'/%3E%3Cpath d='M6 6l4 4m0-4l-4 4' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='8' cy='8' r='6.5' mask='url(%23m)'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-more` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='3.5' cy='8' r='1.25'/%3E%3Ccircle cx='8' cy='8' r='1.25'/%3E%3Ccircle cx='12.5' cy='8' r='1.25'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-grip` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 4h.01M10 4h.01M6 8h.01M10 8h.01M6 12h.01M10 12h.01' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-search` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Ccircle cx='7' cy='7' r='4.5'/%3E%3Cpath d='M10.5 10.5L14 14'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-eye` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Cpath d='M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8z'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-eye-off` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Cpath d='M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8z'/%3E%3Ccircle cx='8' cy='8' r='2'/%3E%3Cpath d='M2.5 2.5l11 11'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-calendar` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Crect x='2.5' y='3.5' width='11' height='10' rx='1.5'/%3E%3Cpath d='M2.5 6.75h11M5.5 2v3M10.5 2v3'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-arrow-right` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 8h11M10 4.5L13.5 8 10 11.5' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |
| `--slean-icon-info` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Ccircle cx='8' cy='8' r='6.25'/%3E%3Cpath d='M8 7.5v3.5M8 5v.01'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-success` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Ccircle cx='8' cy='8' r='6.25'/%3E%3Cpath d='M5.5 8.25l1.75 1.75 3.25-3.5'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-warning` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Cpath d='M6.7 2.75a1.5 1.5 0 012.6 0l5.1 8.9a1.5 1.5 0 01-1.3 2.25H2.9a1.5 1.5 0 01-1.3-2.25z'/%3E%3Cpath d='M8 6.25v3M8 11.5v.01'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-error` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Ccircle cx='8' cy='8' r='6.25'/%3E%3Cpath d='M10 6l-4 4M6 6l4 4'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-file` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'%3E%3Cpath d='M9 1.75H4.5a1.5 1.5 0 00-1.5 1.5v9.5a1.5 1.5 0 001.5 1.5h7a1.5 1.5 0 001.5-1.5V5.75z'/%3E%3Cpath d='M9 1.75v4h4'/%3E%3C/g%3E%3C/svg%3E")` | same as light |
| `--slean-icon-upload` | `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 10.5V2.5m0 0L5 5.5m3-3l3 3M2.5 10.5v1.5a1.5 1.5 0 001.5 1.5h8a1.5 1.5 0 001.5-1.5v-1.5' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")` | same as light |

## Typography

--slean-font-sans is the theme's family: a family list, or \`initial\` (on any element; \`inherit\` on :root does the same) for the page's own font: the token is then invalid, and \`font-family: var(--slean-font-sans)\` falls back to the inherited family. The scale is MUI's set of variants at an application-sized ramp: per variant a weight, a size and a line height (longhands, which the package reads, so the family can be invalid), and a \`font\` shorthand of the three and the family for your own rules (with the family invalid, the whole shorthand is: use the longhands). data-typography="h1" … on any element applies a variant (base.css). Controls (buttons, fields) take the page's family.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-font-sans` | `Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif` | same as light |
| `--slean-font-mono` | `ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace` | same as light |
| `--slean-text-xs` | `0.75rem` | same as light |
| `--slean-text-sm` | `0.875rem` | same as light |
| `--slean-text-md` | `1rem` | same as light |
| `--slean-text-lg` | `1.125rem` | same as light |
| `--slean-leading` | `1.5` | same as light |
| `--slean-font-weight-regular` | `400` | same as light |
| `--slean-font-weight-medium` | `500` | same as light |
| `--slean-font-weight-semibold` | `600` | same as light |
| `--slean-font-weight-bold` | `700` | same as light |
| `--slean-typography-h1-weight` | `700` | same as light |
| `--slean-typography-h1-size` | `2.25rem` | same as light |
| `--slean-typography-h1-line-height` | `1.2` | same as light |
| `--slean-typography-h2-weight` | `700` | same as light |
| `--slean-typography-h2-size` | `1.875rem` | same as light |
| `--slean-typography-h2-line-height` | `1.25` | same as light |
| `--slean-typography-h3-weight` | `600` | same as light |
| `--slean-typography-h3-size` | `1.5rem` | same as light |
| `--slean-typography-h3-line-height` | `1.3` | same as light |
| `--slean-typography-h4-weight` | `600` | same as light |
| `--slean-typography-h4-size` | `1.25rem` | same as light |
| `--slean-typography-h4-line-height` | `1.35` | same as light |
| `--slean-typography-h5-weight` | `600` | same as light |
| `--slean-typography-h5-size` | `1.125rem` | same as light |
| `--slean-typography-h5-line-height` | `1.4` | same as light |
| `--slean-typography-h6-weight` | `600` | same as light |
| `--slean-typography-h6-size` | `1rem` | same as light |
| `--slean-typography-h6-line-height` | `1.5` | same as light |
| `--slean-typography-subtitle1-weight` | `500` | same as light |
| `--slean-typography-subtitle1-size` | `1rem` | same as light |
| `--slean-typography-subtitle1-line-height` | `1.5` | same as light |
| `--slean-typography-subtitle2-weight` | `500` | same as light |
| `--slean-typography-subtitle2-size` | `0.875rem` | same as light |
| `--slean-typography-subtitle2-line-height` | `1.45` | same as light |
| `--slean-typography-body1-weight` | `400` | same as light |
| `--slean-typography-body1-size` | `1rem` | same as light |
| `--slean-typography-body1-line-height` | `1.5` | same as light |
| `--slean-typography-body2-weight` | `400` | same as light |
| `--slean-typography-body2-size` | `0.875rem` | same as light |
| `--slean-typography-body2-line-height` | `1.45` | same as light |
| `--slean-typography-caption-weight` | `400` | same as light |
| `--slean-typography-caption-size` | `0.75rem` | same as light |
| `--slean-typography-caption-line-height` | `1.65` | same as light |
| `--slean-typography-overline-weight` | `500` | same as light |
| `--slean-typography-overline-size` | `0.75rem` | same as light |
| `--slean-typography-overline-line-height` | `2` | same as light |
| `--slean-typography-button-weight` | `500` | same as light |
| `--slean-typography-button-size` | `0.875rem` | same as light |
| `--slean-typography-button-line-height` | `1.25` | same as light |
| `--slean-typography-h1` | `var(--slean-typography-h1-weight) var(--slean-typography-h1-size) / var(--slean-typography-h1-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-h2` | `var(--slean-typography-h2-weight) var(--slean-typography-h2-size) / var(--slean-typography-h2-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-h3` | `var(--slean-typography-h3-weight) var(--slean-typography-h3-size) / var(--slean-typography-h3-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-h4` | `var(--slean-typography-h4-weight) var(--slean-typography-h4-size) / var(--slean-typography-h4-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-h5` | `var(--slean-typography-h5-weight) var(--slean-typography-h5-size) / var(--slean-typography-h5-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-h6` | `var(--slean-typography-h6-weight) var(--slean-typography-h6-size) / var(--slean-typography-h6-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-subtitle1` | `var(--slean-typography-subtitle1-weight) var(--slean-typography-subtitle1-size) / var(--slean-typography-subtitle1-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-subtitle2` | `var(--slean-typography-subtitle2-weight) var(--slean-typography-subtitle2-size) / var(--slean-typography-subtitle2-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-body1` | `var(--slean-typography-body1-weight) var(--slean-typography-body1-size) / var(--slean-typography-body1-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-body2` | `var(--slean-typography-body2-weight) var(--slean-typography-body2-size) / var(--slean-typography-body2-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-caption` | `var(--slean-typography-caption-weight) var(--slean-typography-caption-size) / var(--slean-typography-caption-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-overline` | `var(--slean-typography-overline-weight) var(--slean-typography-overline-size) / var(--slean-typography-overline-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-button` | `var(--slean-typography-button-weight) var(--slean-typography-button-size) / var(--slean-typography-button-line-height) var(--slean-font-sans)` | same as light |
| `--slean-typography-button-transform` | `none` | same as light |
| `--slean-typography-overline-transform` | `uppercase` | same as light |

## Motion

The indicator duration is one turn of a loading indicator (the busy button and the circular progress); reduced motion slows it instead of stopping it, so a still ring never reads as a stuck page.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-duration-fast` | `100ms` | same as light |
| `--slean-duration-normal` | `160ms` | same as light |
| `--slean-duration-slow` | `240ms` | same as light |
| `--slean-duration-indicator` | `750ms` | same as light |
| `--slean-ease` | `cubic-bezier(0.2, 0, 0, 1)` | same as light |
| `--slean-ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | same as light |
| `--slean-ease-out` | `cubic-bezier(0, 0, 0.2, 1)` | same as light |
| `--slean-ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | same as light |
| `--slean-ease-sharp` | `cubic-bezier(0.4, 0, 0.6, 1)` | same as light |

## Stacking

for application chrome outside the top layer (a sticky app bar, an in-page drawer). Dialogs, popovers, menus, tooltips and toasts live in the browser's top layer, above every z-index; these values only order what is not in it.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-z-app-bar` | `1100` | same as light |
| `--slean-z-drawer` | `1200` | same as light |
| `--slean-z-modal` | `1300` | same as light |
| `--slean-z-snackbar` | `1400` | same as light |
| `--slean-z-tooltip` | `1500` | same as light |

## Breakpoints

for reference and container queries (a custom property cannot appear in a media query): xs 0, sm 600px, md 900px, lg 1200px, xl 1536px.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-breakpoint-xs` | `0px` | same as light |
| `--slean-breakpoint-sm` | `600px` | same as light |
| `--slean-breakpoint-md` | `900px` | same as light |
| `--slean-breakpoint-lg` | `1200px` | same as light |
| `--slean-breakpoint-xl` | `1536px` | same as light |

## Focus ring geometry

used by every :focus-visible outline.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-focus-ring-width` | `2px` | same as light |
| `--slean-focus-ring-offset` | `2px` | same as light |

## Inputs written as inline styles on a root

not theme tokens: the range slider's two values and the splitter's first pane (written by their behaviors), and the value of a circular progress (written by the author next to value), as percentages without the unit; the color of one swatch of a swatch radio group (written by the author on its label).

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-range-start` | `0` | same as light |
| `--slean-range-end` | `100` | same as light |
| `--slean-split` | `50` | same as light |
| `--slean-value` | `0` | same as light |
| `--slean-swatch` | `transparent` | same as light |
| `--slean-color-scheme` | `light` | `dark` |

## Neutral scale

1 = page background, 12 = strongest text. 8 is the control border, at least 3:1 against the surface and the page background in both themes.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-neutral-1` | `oklch(99.5% 0.002 260)` | `oklch(16.5% 0.008 260)` |
| `--slean-neutral-2` | `oklch(98.2% 0.003 260)` | `oklch(19.5% 0.009 260)` |
| `--slean-neutral-3` | `oklch(95.8% 0.004 260)` | `oklch(23% 0.01 260)` |
| `--slean-neutral-4` | `oklch(93.2% 0.005 260)` | `oklch(26% 0.011 260)` |
| `--slean-neutral-5` | `oklch(90.5% 0.006 260)` | `oklch(29.5% 0.012 260)` |
| `--slean-neutral-6` | `oklch(87.2% 0.007 260)` | `oklch(33.5% 0.013 260)` |
| `--slean-neutral-7` | `oklch(80.8% 0.009 260)` | `oklch(39.5% 0.014 260)` |
| `--slean-neutral-8` | `oklch(63.5% 0.012 260)` | `oklch(53% 0.015 260)` |
| `--slean-neutral-9` | `oklch(55.2% 0.015 260)` | `oklch(58% 0.015 260)` |
| `--slean-neutral-10` | `oklch(50.8% 0.015 260)` | `oklch(63% 0.015 260)` |
| `--slean-neutral-11` | `oklch(44.6% 0.014 260)` | `oklch(74% 0.012 260)` |
| `--slean-neutral-12` | `oklch(20.5% 0.012 260)` | `oklch(95% 0.005 260)` |

## Palette (MUI-style)

per color a main tone, a lighter and a darker one, the text that sits on main (contrast-text), and the tint and text of the standard (soft) variants. Neutral is derived from the neutral scale in the alias block. Every pair a variant draws meets WCAG AA (tests/contrast.test.ts): contrast-text on main and dark at least 4.5:1, main as text on the surface, the page and its own hover tint at least 4.5:1, soft-text on soft at least 4.5:1. In light the mains are dark enough for white text; in dark they are light, with dark text, as in MUI's dark palette.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-primary-main` | `oklch(51% 0.18 258)` | `oklch(71% 0.13 258)` |
| `--slean-primary-light` | `oklch(64% 0.144 258)` | `oklch(80% 0.099 258)` |
| `--slean-primary-dark` | `oklch(45% 0.165 258)` | `oklch(64% 0.13 258)` |
| `--slean-primary-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 258)` |
| `--slean-primary-soft` | `oklch(95% 0.02 258)` | `oklch(28% 0.07 258)` |
| `--slean-primary-soft-text` | `oklch(42% 0.155 258)` | `oklch(85% 0.07 258)` |
| `--slean-secondary-main` | `oklch(52% 0.2 305)` | `oklch(72% 0.13 305)` |
| `--slean-secondary-light` | `oklch(64% 0.16 305)` | `oklch(81% 0.104 305)` |
| `--slean-secondary-dark` | `oklch(45% 0.2 305)` | `oklch(65% 0.13 305)` |
| `--slean-secondary-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 305)` |
| `--slean-secondary-soft` | `oklch(95.5% 0.025 305)` | `oklch(28% 0.08 305)` |
| `--slean-secondary-soft-text` | `oklch(43% 0.17 305)` | `oklch(86% 0.08 305)` |
| `--slean-error-main` | `oklch(52% 0.19 25)` | `oklch(72% 0.13 25)` |
| `--slean-error-light` | `oklch(64% 0.152 25)` | `oklch(81% 0.104 25)` |
| `--slean-error-dark` | `oklch(45% 0.18 25)` | `oklch(65% 0.13 25)` |
| `--slean-error-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 25)` |
| `--slean-error-soft` | `oklch(95.5% 0.02 25)` | `oklch(27% 0.08 25)` |
| `--slean-error-soft-text` | `oklch(45% 0.18 25)` | `oklch(85% 0.08 25)` |
| `--slean-warning-main` | `oklch(52% 0.125 55)` | `oklch(76% 0.14 55)` |
| `--slean-warning-light` | `oklch(64% 0.104 55)` | `oklch(85% 0.092 55)` |
| `--slean-warning-dark` | `oklch(45% 0.11 55)` | `oklch(69% 0.14 55)` |
| `--slean-warning-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 55)` |
| `--slean-warning-soft` | `oklch(96% 0.04 85)` | `oklch(28% 0.055 80)` |
| `--slean-warning-soft-text` | `oklch(45% 0.09 75)` | `oklch(87% 0.12 85)` |
| `--slean-info-main` | `oklch(50% 0.105 235)` | `oklch(72% 0.11 235)` |
| `--slean-info-light` | `oklch(62% 0.088 235)` | `oklch(81% 0.088 235)` |
| `--slean-info-dark` | `oklch(43% 0.09 235)` | `oklch(65% 0.11 235)` |
| `--slean-info-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 235)` |
| `--slean-info-soft` | `oklch(95.5% 0.025 235)` | `oklch(28% 0.055 235)` |
| `--slean-info-soft-text` | `oklch(43% 0.09 235)` | `oklch(86% 0.08 235)` |
| `--slean-success-main` | `oklch(49% 0.13 150)` | `oklch(72% 0.14 150)` |
| `--slean-success-light` | `oklch(61% 0.104 150)` | `oklch(81% 0.112 150)` |
| `--slean-success-dark` | `oklch(42% 0.115 150)` | `oklch(65% 0.14 150)` |
| `--slean-success-contrast-text` | `oklch(100% 0 0)` | `oklch(18% 0.02 150)` |
| `--slean-success-soft` | `oklch(95.5% 0.04 150)` | `oklch(27% 0.07 150)` |
| `--slean-success-soft-text` | `oklch(40% 0.11 150)` | `oklch(85% 0.12 150)` |

## Surfaces that differ between the themes

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-surface` | `oklch(100% 0 0)` | `var(--slean-neutral-2)` |
| `--slean-muted` | `var(--slean-neutral-3)` | `var(--slean-neutral-4)` |
| `--slean-muted-hover` | `var(--slean-neutral-4)` | `var(--slean-neutral-5)` |
| `--slean-overlay` | `oklch(0% 0 0 / 0.4)` | `oklch(0% 0 0 / 0.6)` |

## Elevation 0-5 (MUI's 0-24 compressed)

resting surfaces 1, raised cards 2, menus and popovers 3, dialogs 4, drawers and toasts 5.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-elevation-0` | `none` | `none` |
| `--slean-elevation-1` | `0 1px 2px oklch(0% 0 0 / 0.08)` | `0 1px 2px oklch(0% 0 0 / 0.4)` |
| `--slean-elevation-2` | `0 2px 6px oklch(0% 0 0 / 0.08), 0 1px 2px oklch(0% 0 0 / 0.06)` | `0 2px 6px oklch(0% 0 0 / 0.45), 0 1px 2px oklch(0% 0 0 / 0.35)` |
| `--slean-elevation-3` | `0 4px 12px oklch(0% 0 0 / 0.1), 0 1px 3px oklch(0% 0 0 / 0.08)` | `0 4px 12px oklch(0% 0 0 / 0.5), 0 1px 3px oklch(0% 0 0 / 0.4)` |
| `--slean-elevation-4` | `0 16px 40px oklch(0% 0 0 / 0.18), 0 2px 6px oklch(0% 0 0 / 0.08)` | `0 16px 40px oklch(0% 0 0 / 0.6), 0 2px 6px oklch(0% 0 0 / 0.4)` |
| `--slean-elevation-5` | `0 24px 56px oklch(0% 0 0 / 0.22), 0 4px 10px oklch(0% 0 0 / 0.1)` | `0 24px 56px oklch(0% 0 0 / 0.65), 0 4px 10px oklch(0% 0 0 / 0.45)` |

## Semantic surfaces and text

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-bg` | `var(--slean-neutral-1)` | same as light |
| `--slean-fg` | `var(--slean-neutral-12)` | same as light |
| `--slean-fg-muted` | `var(--slean-neutral-11)` | same as light |
| `--slean-border` | `var(--slean-neutral-6)` | same as light |
| `--slean-border-strong` | `var(--slean-neutral-8)` | same as light |

## Neutral

the seventh color: the grey scale as a palette.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-neutral-main` | `var(--slean-neutral-11)` | same as light |
| `--slean-neutral-light` | `var(--slean-neutral-8)` | same as light |
| `--slean-neutral-dark` | `var(--slean-neutral-12)` | same as light |
| `--slean-neutral-contrast-text` | `var(--slean-neutral-1)` | same as light |
| `--slean-neutral-soft` | `var(--slean-neutral-3)` | same as light |
| `--slean-neutral-soft-text` | `var(--slean-neutral-12)` | same as light |

## The current color

what data-color selects (block 4), primary by default. A component draws its variants from these eight tokens only, so every color works with every variant.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-color-main` | `var(--slean-primary-main)` | same as light |
| `--slean-color-light` | `var(--slean-primary-light)` | same as light |
| `--slean-color-dark` | `var(--slean-primary-dark)` | same as light |
| `--slean-color-contrast-text` | `var(--slean-primary-contrast-text)` | same as light |
| `--slean-color-soft` | `var(--slean-primary-soft)` | same as light |
| `--slean-color-soft-text` | `var(--slean-primary-soft-text)` | same as light |

## The ink and the line of the outlined and text variants

the main tone and a half-tone line of it for every color; for neutral, MUI's "inherit": the text color and the border hairline (block 4).

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-color-text` | `var(--slean-primary-main)` | same as light |
| `--slean-color-border` | `color-mix(in oklch, var(--slean-primary-main) 50%, transparent)` | same as light |

## Control shell (ADR 0007)

the field every form control shares (input, select, autocomplete, date picker and the other fields). The border is at least 3:1 against the surface; hover and focus take the primary color and the focus ring is a soft halo of it; a disabled control sits on the muted surface.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-control-bg` | `var(--slean-surface)` | same as light |
| `--slean-control-bg-disabled` | `var(--slean-muted)` | same as light |
| `--slean-control-border-disabled` | `var(--slean-border)` | same as light |
| `--slean-control-border` | `var(--slean-border-strong)` | same as light |
| `--slean-control-ring-error` | `0 0 0 3px color-mix(in oklch, var(--slean-error-main) 22%, transparent)` | same as light |
| `--slean-control-ring-warning` | `0 0 0 3px color-mix(in oklch, var(--slean-warning-main) 28%, transparent)` | same as light |

## The surface of data-variant="filled" fields and the line of "standard" ones

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-control-filled-bg` | `var(--slean-muted)` | same as light |
| `--slean-control-filled-bg-hover` | `var(--slean-muted-hover)` | same as light |
| `--slean-control-icon` | `var(--slean-fg-muted)` | same as light |

## Option rows of every listbox (select

autocomplete, the native picker): the active row (hover or keyboard position) and the chosen one, and the row's own metrics. A theme that marks the chosen row with its check alone sets the ring to none and the weight to the normal one.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-option-active` | `var(--slean-muted)` | same as light |
| `--slean-option-active-fg` | `var(--slean-fg)` | same as light |
| `--slean-option-selected` | `var(--slean-primary-soft)` | same as light |
| `--slean-option-selected-fg` | `var(--slean-primary-soft-text)` | same as light |
| `--slean-option-selected-weight` | `var(--slean-font-weight-semibold)` | same as light |
| `--slean-option-selected-ring` | `inset 0 0 0 1px var(--slean-option-selected-fg)` | same as light |
| `--slean-option-min-height` | `var(--slean-control-height-sm)` | same as light |
| `--slean-option-padding-block` | `var(--slean-space-1)` | same as light |
| `--slean-option-padding-inline` | `var(--slean-space-2)` | same as light |
| `--slean-popup-radius` | `var(--slean-radius-md)` | same as light |
| `--slean-popup-shadow` | `var(--slean-elevation-3)` | same as light |

## Component defaults a theme sets in one place (MUI's theme.components)

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-button-radius` | `var(--slean-radius-md)` | same as light |

## A font shorthand; button.css takes the family from the page

so the family here is only a placeholder and --slean-font-sans may be anything, \`initial\` included.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-button-font` | `var(--slean-typography-button-weight) var(--slean-typography-button-size) / var(--slean-typography-button-line-height) sans-serif` | same as light |
| `--slean-button-transform` | `var(--slean-typography-button-transform)` | same as light |
| `--slean-field-radius` | `var(--slean-radius-md)` | same as light |
| `--slean-card-radius` | `var(--slean-radius-lg)` | same as light |
| `--slean-card-elevation` | `var(--slean-elevation-1)` | same as light |
| `--slean-dialog-radius` | `var(--slean-radius-lg)` | same as light |
| `--slean-dialog-elevation` | `var(--slean-elevation-4)` | same as light |

## The dialog's padding

its content inset, the bleed of its sticky header and footer and their sticky offsets all follow it, so a denser theme changes one token.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-dialog-padding` | `var(--slean-space-6)` | same as light |
| `--slean-chip-radius` | `var(--slean-radius-full)` | same as light |

## Focus ring

used by every :focus-visible outline.

| Token | Light | Dark |
| --- | --- | --- |
| `--slean-focus-ring-color` | `var(--slean-primary-main)` | same as light |
| `--slean-focus-ring` | `var(--slean-focus-ring-width) solid var(--slean-focus-ring-color)` | same as light |

## Media adjustments

### Forced colors

Author-controlled tokens map to system colors. Query: `@media (forced-colors: active)`.

| Token | Value under the query |
| --- | --- |
| `--slean-focus-ring-color` | `Highlight` |
| `--slean-border` | `CanvasText` |
| `--slean-border-strong` | `CanvasText` |
| `--slean-elevation-1` | `none` |
| `--slean-elevation-2` | `none` |
| `--slean-elevation-3` | `none` |
| `--slean-elevation-4` | `none` |
| `--slean-elevation-5` | `none` |

### Reduced motion

Transition durations become zero; the loading indicator duration grows longer. Query: `@media (prefers-reduced-motion: reduce)`.

| Token | Value under the query |
| --- | --- |
| `--slean-duration-fast` | `0ms` |
| `--slean-duration-normal` | `0ms` |
| `--slean-duration-slow` | `0ms` |
| `--slean-duration-indicator` | `1600ms` |

### Coarse pointer

Every control height grows and the small one reaches the pointer target size; checkbox and switch add their own hit area. Query: `@media (pointer: coarse)`.

| Token | Value under the query |
| --- | --- |
| `--slean-control-height-xs` | `2.25rem` |
| `--slean-control-height-sm` | `2.5rem` |
| `--slean-control-height-md` | `2.75rem` |
| `--slean-control-height-lg` | `3rem` |

## Source

packages/styles/css/tokens.css, parsed by tokens.ts. scripts/check.mjs fails on a token defined in a component file, an unknown token, or differing dark blocks. Minified size: 4041 B gzip · 3369 B brotli.
