Tokens
Every --slean-* custom property of @svelte-lean/styles, read from tokens.css when this page is built. Light values are the defaults on :root; dark values apply under prefers-color-scheme: dark or a data-theme attribute on the root.
On this page
Definition
Tokens are custom properties on :root inside the slean.tokens cascade
layer, defined in oklch. Component files reference tokens and define none of their
own; every var(--slean-…) in the package resolves to a token on this page. An unlayered
override in your application always wins:
/* A theme is a set of token values. Redefine them on :root to retheme every primitive. */
:root {
--slean-accent: oklch(45% 0.13 150);
--slean-accent-hover: oklch(40% 0.13 150);
--slean-accent-active: oklch(36% 0.12 150);
--slean-radius-md: 0.25rem;
--slean-font-sans: 'IBM Plex Sans', system-ui, sans-serif;
}Semantic tokens (--slean-bg, --slean-fg, --slean-border)
reference the neutral scale, so changing the scale retints every surface. A swatch shows the
resolved default of the package; the controls on this site read the site's own palette through a
token bridge instead (Themes).
Shape and spacing
| Token | Light | Dark |
|---|---|---|
--slean-radius-sm | 0.375rem | same as light |
--slean-radius-md | 0.625rem | same as light |
--slean-radius-lg | 0.875rem | same as light |
--slean-radius-full | 9999px | same as light |
--slean-space-1 | 0.25rem | same as light |
--slean-space-2 | 0.5rem | same as light |
--slean-space-3 | 0.75rem | same as light |
--slean-space-4 | 1rem | same as light |
--slean-space-5 | 1.25rem | same as light |
--slean-space-6 | 1.5rem | same as light |
--slean-space-7 | 2rem | same as light |
--slean-space-8 | 2.5rem | same as light |
Control heights
sm 32px, md 36px (the default of every form control, button and bar), lg 44px. A coarse pointer raises them to 40, 44 and 48px.
| Token | Light | Dark |
|---|---|---|
--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 |
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-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-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-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 |
Typography
Controls inherit the page font; apply --slean-font-sans to the page yourself.
| 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-medium | 500 | same as light |
--slean-font-weight-semibold | 600 | same as light |
Motion
The indicator duration is one turn of a loading indicator (the busy button and the spinner); 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 |
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 a behavior writes as inline styles on its root
not theme tokens: the range slider's two values and the splitter's first pane, as percentages without the unit.
| Token | Light | Dark |
|---|---|---|
--slean-range-start | 0 | same as light |
--slean-range-end | 100 | same as light |
--slean-split | 50 | 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) |
Accent
| Token | Light | Dark |
|---|---|---|
--slean-accent | oklch(54% 0.19 258) | oklch(56% 0.18 258) |
--slean-accent-hover | oklch(49% 0.19 258) | oklch(61% 0.18 258) |
--slean-accent-active | oklch(45% 0.18 258) | oklch(52% 0.18 258) |
--slean-accent-fg | oklch(100% 0 0) | oklch(100% 0 0) |
--slean-accent-soft | oklch(95% 0.03 258) | oklch(28% 0.07 258) |
--slean-accent-soft-fg | oklch(42% 0.17 258) | oklch(85% 0.09 258) |
Status
| Token | Light | Dark |
|---|---|---|
--slean-success | oklch(55% 0.15 150) | oklch(58% 0.15 150) |
--slean-success-fg | oklch(100% 0 0) | oklch(100% 0 0) |
--slean-success-soft | oklch(95.5% 0.04 150) | oklch(27% 0.07 150) |
--slean-success-soft-fg | oklch(40% 0.12 150) | oklch(85% 0.12 150) |
--slean-warning | oklch(76% 0.16 80) | oklch(80% 0.16 80) |
--slean-warning-fg | oklch(25% 0.05 80) | oklch(20% 0.05 80) |
--slean-warning-soft | oklch(96% 0.05 85) | oklch(28% 0.07 80) |
--slean-warning-soft-fg | oklch(45% 0.12 75) | oklch(87% 0.12 85) |
--slean-danger | oklch(55% 0.2 25) | oklch(58% 0.19 25) |
--slean-danger-hover | oklch(50% 0.2 25) | oklch(63% 0.19 25) |
--slean-danger-fg | oklch(100% 0 0) | oklch(100% 0 0) |
--slean-danger-soft | oklch(95.5% 0.03 25) | oklch(27% 0.08 25) |
--slean-danger-soft-fg | oklch(45% 0.18 25) | oklch(85% 0.1 25) |
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) |
--slean-shadow-sm | 0 1px 2px oklch(0% 0 0 / 0.08) | 0 1px 2px oklch(0% 0 0 / 0.4) |
--slean-shadow-md | 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-shadow-lg | 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) |
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 |
Info follows the accent unless a theme gives it a color of its own
| Token | Light | Dark |
|---|---|---|
--slean-info | var(--slean-accent) | same as light |
--slean-info-soft | var(--slean-accent-soft) | same as light |
--slean-info-soft-fg | var(--slean-accent-soft-fg) | same as light |
Control shell (ADR 0007)
the field every form control shares (input, select, combobox, date picker and the other fields). The border is at least 3:1 against the surface; hover and focus take the accent 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-border-hover | var(--slean-accent) | same as light |
--slean-control-border-focus | var(--slean-accent) | same as light |
--slean-control-ring | 0 0 0 3px color-mix(in oklch, var(--slean-accent) 22%, transparent) | same as light |
--slean-control-ring-danger | 0 0 0 3px color-mix(in oklch, var(--slean-danger) 22%, transparent) | same as light |
--slean-control-ring-warning | 0 0 0 3px color-mix(in oklch, var(--slean-warning) 28%, transparent) | same as light |
--slean-control-icon | var(--slean-fg-muted) | same as light |
--slean-option-active | var(--slean-muted) | same as light |
--slean-option-selected | var(--slean-accent-soft) | same as light |
--slean-option-selected-fg | var(--slean-accent-soft-fg) | same as light |
--slean-popup-radius | var(--slean-radius-md) | same as light |
--slean-popup-shadow | var(--slean-shadow-lg) | same as light |
Focus ring
used by every :focus-visible outline.
| Token | Light | Dark |
|---|---|---|
--slean-focus-ring-color | var(--slean-accent) | same as light |
--slean-focus-ring | var(--slean-focus-ring-width) solid var(--slean-focus-ring-color) | same as light |
Media adjustments
Three media queries in the same file change token values for the whole document. They are listed here from the file as well.
Forced colors
The platform replaces most colors itself; only the tokens that stay author-controlled are mapped 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-shadow-sm | none |
--slean-shadow-md | none |
--slean-shadow-lg | none |
Reduced motion
Every duration becomes zero. Component files read the duration tokens and never a raw millisecond value, so every transition stops without a second rule. 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
The small control height grows to the pointer target size; checkbox and switch add an invisible hit area of their own. Query: @media (pointer: coarse).
| Token | Value under the query |
|---|---|
--slean-control-height-sm | 2.5rem |
--slean-control-height-md | 2.75rem |
--slean-control-height-lg | 3rem |
Source
packages/styles/css/tokens.css is the file this page is generated from; its parser is apps/docs/src/lib/data/tokens.ts. The package's static check (scripts/check.mjs) fails when a component file defines a token, references one that does not exist, or when the two dark blocks differ. Measured size of the file, minified: 2204 B gzip · 1895 B brotli.