Design Tokens
The complete CSS-variable surface that every WireKit component reads from. Override any variable in your :root {} (light) or .dark {} (dark mode) block to retheme the library — no Tailwind config changes, no component overrides, no rebuild needed.
Looking for setup help, theme presets, or accessibility guarantees? See the Theming guide. This page is the pure variable reference.
All Design Tokens
Colors — Accent
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-accent |
neutral-900 | neutral-50 | Accent fill — button / badge backgrounds |
--color-wk-accent-hover |
neutral-800 | neutral-200 | Accent fill hover |
--color-wk-accent-text |
neutral-900 | neutral-50 | Accent-toned text on the page background (links, stepper, callout) — alias of --color-wk-accent-content |
--color-wk-accent-content |
neutral-900 | neutral-50 | Readable accent text on the page or a soft-tinted accent surface |
--color-wk-accent-fg |
neutral-50 | neutral-900 | Text/icon on an accent fill |
Three accent roles, don't mix them.
--color-wk-accentis a fill — it sits behind other content and can be a bright, saturated brand color. Never use it as a text color: on a light brand accent, text drawn in the fill color fails contrast against the page background. Accent-toned text reads--color-wk-accent-text(an alias of--color-wk-accent-content), which defaults to a near-black/near-white that always clears WCAG AA. Text that sits on an accent fill (a button label) reads--color-wk-accent-fg. When you re-tint, set--color-wk-accentto your brand fill and--color-wk-accent-contentto a readable text tone of it — otherwise links fall back to the safe near-neutral default.
Colors — Danger
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-danger |
red-600 | red-600 | Danger button bg |
--color-wk-danger-hover |
red-700 | red-700 | Danger button hover |
--color-wk-danger-fg |
white | white | Text on danger bg |
--color-wk-danger-text |
red-700 | red-400 | Error message text |
Colors — Success
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-success |
green-600 | green-500 | Success states |
--color-wk-success-hover |
green-500 | green-400 | Success hover |
--color-wk-success-fg |
zinc-900 | zinc-900 | Text on success bg |
--color-wk-success-text |
oklch(45.6% 0.17 149.7) | green-400 | Success message text |
--color-wk-border-success |
= success-text | = success-text | Success-state border on a form control. Deliberately the same value as --color-wk-success-text: the border and the helper text below it read as one state, and a divergence looks like a rendering fault rather than a design choice. Re-tint both together. |
Colors — Warning
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-warning |
amber-500 | amber-400 | Warning states |
--color-wk-warning-hover |
amber-600 | amber-300 | Warning hover |
--color-wk-warning-fg |
zinc-900 | zinc-900 | Text on warning bg |
--color-wk-warning-text |
oklch(47.6% 0.158 65) | oklch(85.4% 0.183 81) | Warning message text — the readable tone, not the fill. --color-wk-warning is a fill and fails contrast as body text on the page background. |
--color-wk-warning-bg |
oklch(96.4% 0.058 102.1) | deep amber | Softly tinted warning surface (alert, callout). Pale enough that --color-wk-text stays the body color on top of it, so the tint carries the state and the text carries the reading. |
Colors — Neutrals
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-bg |
white | neutral-950 | Page background |
--color-wk-bg-elevated |
white | neutral-900 | Cards, modals |
--color-wk-bg-subtle |
neutral-50 | neutral-900 | Hover backgrounds |
--color-wk-bg-muted |
neutral-100 | neutral-800 | Secondary button bg |
--color-wk-bg-input |
white | neutral-950 | Input backgrounds |
--color-wk-text |
neutral-950 | neutral-50 | Primary text |
--color-wk-text-muted |
oklch(47%) | oklch(74%) | Hint text |
--color-wk-text-subtle |
neutral-500 | neutral-400 | Prefix/suffix text |
--color-wk-text-placeholder |
neutral-500 | neutral-400 | Input placeholders |
--color-wk-code |
= text | = text | Inline <code> text — themeable per preset (Aurora sets it to the brand magenta) |
--color-wk-code-bg |
= bg-muted | = bg-muted | Inline <code> background — themeable per preset |
--color-wk-bg-inverse |
neutral-950 | neutral-50 | Background of a surface that deliberately opposes the page — a dark band in a light theme and a light one in a dark theme. It flips with the theme, which is the point: "inverse" is a relationship, not a color. Pair it with --color-wk-text-inverse. |
--color-wk-text-inverse |
white | neutral-950 | Text on an inverse surface. Always used together with --color-wk-bg-inverse; setting one without the other is how an inverted band ends up with unreadable text. |
--color-wk-bg-skeleton |
oklch(93%) | oklch(33%) | Resting fill of a loading placeholder (<x-wirekit::skeleton>). Distinct from --color-wk-bg-muted on purpose — a placeholder should read as absent content, not as a quiet surface. |
Colors — Info
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-info-text |
= accent-content | = accent-content | Informational message text. Defaults to an alias of --color-wk-accent-content, so an info note inherits your accent's readable tone rather than a second blue you would have to keep in sync. |
There is no
--color-wk-border-infoin the stylesheet, and that is deliberate. No component renders an informational border — a form control is either in its error state or its resting one, with no third — so a token for it would exist only to be overridden into nothing. (--color-wk-infois a different token and does exist: it is the fifth chart dataset color, listed under Chart Theming below, and unrelated to this text token.)
Colors — Borders and Focus
| Variable | Light | Dark | Used for |
|---|---|---|---|
--color-wk-border |
oklch(91.4%) | oklch(32%) | Decorative borders (cards, dividers, tables) — WCAG 1.4.11-exempt, kept light |
--color-wk-border-strong |
oklch(66%) | oklch(51%) | Resting border for every form control — meets WCAG 1.4.11 (3:1) against the input fill |
--color-wk-border-strong-hover |
oklch(58%) | oklch(58%) | Hover border for every form control — moves further from the fill (4.28:1 light, 4.62:1 dark) |
--color-wk-border-hover |
oklch(80%) | neutral-600 | Hover state for decorative borders only — does not meet 3:1, never use it on a control |
--color-wk-border-subtle |
oklch(94%) | neutral-800 | Subtle dividers |
--color-wk-border-error |
= danger-text | = danger-text | Error-state border on a form control. Deliberately the same value as --color-wk-danger-text: the border and the message below it read as one state, and a divergence looks like a rendering fault rather than a design choice. Re-tint both together. |
--color-wk-ring |
neutral-950 | neutral-50 | Focus rings |
--color-wk-ring-offset |
white | neutral-950 | Focus ring offset |
Border Radius
All radius values derive from a single base --radius-wk. Change one value, everything scales.
| Variable | Default | Used for |
|---|---|---|
--radius-wk |
0.5rem | Base value |
--radius-wk-sm |
calc(base * 0.75) | Small buttons (xs, sm) |
--radius-wk-md |
base | Default buttons, inputs |
--radius-wk-lg |
calc(base * 1.25) | Large buttons (xl) |
--radius-wk-xl |
calc(base * 2) | Cards, modals |
--radius-wk-full |
9999px | Pills, avatars |
Shadows
| Variable | Default | Used for |
|---|---|---|
--shadow-wk-sm |
Subtle 1px shadow | Buttons, inputs |
--shadow-wk-md |
Medium elevation | Dropdowns |
--shadow-wk-lg |
Strong elevation | Modals |
--shadow-wk-none |
transparent | Ghost buttons |
--shadow-wk-badge |
1px, text-tinted | The badge's own lift. Tinted from --color-wk-text via color-mix() rather than from black, so it stays a shadow of the theme instead of a gray smudge on a colored surface |
Typography
| Variable | Default | Used for |
|---|---|---|
--font-wk-sans |
Inter, system-ui | All components |
--font-wk-mono |
Fira Code, monospace | Code elements |
--font-wk-heading-weight |
600 | Headings |
--font-wk-body-weight |
400 | Body text, buttons |
--font-wk-letter-spacing |
-0.01em | All text. Slightly negative because Inter is drawn a touch loose at body sizes; a font preset that is not may want it back at 0em |
--font-wk-line-height |
1.5 | All text |
Motion
| Variable | Default | Used for |
|---|---|---|
--transition-wk-duration |
150ms | All transitions (back-compat name; same value as --motion-wk-duration-fast) |
--transition-wk-easing |
cubic-bezier(0.4, 0, 0.2, 1) | All transitions |
--motion-wk-duration-fast |
150ms | Fast animations (<x-wirekit::reveal>, .wk-animate-fast) |
--motion-wk-duration-normal |
300ms | Normal animations (default for <x-wirekit::reveal>) |
--motion-wk-duration-slow |
600ms | Slow animations (.wk-animate-slow) |
--motion-wk-easing-out |
cubic-bezier(0.16, 1, 0.3, 1) | Decelerating easing (elements entering the screen) |
--motion-wk-easing-in |
cubic-bezier(0.7, 0, 0.84, 0) | Accelerating easing (elements leaving the screen) |
--shimmer-wk-duration |
2s | Travel time of one sweep across <x-wirekit::shimmer>. Overridable per instance through the component's duration prop |
--shimmer-wk-band |
3ch | Half-width of the bright band in that sweep. Smaller reads as a tight glint, larger as a broad wash. In ch so the band scales with the text it travels over rather than with the viewport |
--shimmer-wk-angle |
105deg | Tilt of the band. 90deg gives a vertical sweep |
--wk-stagger-step |
75ms | Delay added per child in a staggered entrance (<x-wirekit::stats stagger>). The n-th child waits n × this, so the number is a rhythm rather than a duration |
--motion-wk-easing-spring |
cubic-bezier(0.5, 1.5, 0.5, 1) | Spring/overshoot easing (bounce, spring presets) |
--motion-wk-delay-none |
0ms | <x-wirekit::reveal delay="none"> |
--motion-wk-delay-sm |
75ms | <x-wirekit::reveal delay="sm"> (also the default feature-grid stagger step) |
--motion-wk-delay-md |
150ms | <x-wirekit::reveal delay="md"> |
--motion-wk-delay-lg |
300ms | <x-wirekit::reveal delay="lg"> |
--motion-wk-delay-xl |
500ms | <x-wirekit::reveal delay="xl"> |
The four shimmer and stagger tokens above are inert under prefers-reduced-motion: reduce,
like everything in this section.
Spacing and Layout
These tokens drive the gaps and padding inside the layout primitives (stack, row, grid, section) and component internals. WireKit components carry no outer margins — compose vertical and horizontal rhythm with the layout primitives + their gap prop instead of hand-rolling space-y-* / mb-* utilities.
| Variable | Default | Used for |
|---|---|---|
--space-wk-nav-gap |
0.25rem |
The gap between rows in a navigation column. Read by BOTH the app rail and the sidebar, because a console shell puts the two side by side and a reader sees any difference between them as a fault rather than as a choice |
--space-wk-xs … --space-wk-2xl |
0.25 · 0.5 · 1 · 1.5 · 2.5 · 4rem | General spacing scale (xs, sm, md, lg, xl, 2xl) — and what the gap prop of stack / row / grid / bento-grid / feature-grid resolves to by default |
--space-wk-section-sm / -md / -lg |
3rem / 5rem / 7rem | Vertical padding for page sections (section, hero, cta) |
--gap-wk-xs … -2xl |
0.25 / 0.5 / 0.75 / 1 / 1.5 / 2rem | Gaps INSIDE a component — icon to label, footer buttons, dropdown items. A layout primitive reads the --space-wk-* ladder above unless you ask for this one: stack, row, grid, bento-grid and feature-grid take scale="gap" for exactly that, which is the only way a gap prop reaches these rungs. Full xs · sm · md · lg · xl · 2xl ladder |
--padding-wk-x-xs … -xl |
0.375 · 0.625 · 0.75 · 1 · 1.5rem | Horizontal (inline) padding, axis-split |
--padding-wk-y-xs … -xl |
0.25 · 0.375 · 0.5 · 0.75 · 1rem | Vertical (block) padding, axis-split |
The padding scale is axis-split (-x- / -y-) with no axis-less shorthand; the gap scale runs the full xs…2xl ladder (a reference to an undefined step would silently collapse the gap to 0, so the ladder is complete).
Border Width
| Variable | Default | Used for |
|---|---|---|
--border-wk-width |
1px | All component borders |
--border-wk-badge-width |
= border-wk-width | The badge's ring, as its own knob. It defaults to the global width and exists so a preset can hairline the badge alone — the Brutalist preset thickens every border, and a badge at that weight reads as a button |
States
| Variable | Default | Used for |
|---|---|---|
--opacity-wk-hover |
0.8 | Hover opacity |
--opacity-wk-disabled |
0.5 | Disabled elements |
--opacity-wk-loading |
0.7 | Loading states |
Focus Ring
| Variable | Default | Used for |
|---|---|---|
--ring-wk-width |
2px | Focus ring width |
--ring-wk-offset |
2px | Focus ring offset |
Component Sizing
| Variable | Default | Used for |
|---|---|---|
--size-wk-xs |
1.5rem (24px) | Smallest control box — the checkbox and radio square, a badge dot. Below --size-wk-touch-target by design: the target around it is what has to clear that, not the painted box |
--size-wk-sm |
2rem (32px) | Small button/input height |
--size-wk-md |
2.5rem (40px) | Default button/input height |
--size-wk-md-compact |
2.25rem (36px) | A shade under the default, for a control sitting inside another control — a toolbar button, an input addon — where the full height would crowd its container |
--size-wk-lg |
3rem (48px) | Large button/input height |
--size-wk-xl |
6rem (96px) | Not a control height: the box for a large avatar, an empty-state glyph, a media thumbnail |
--size-wk-touch-target |
2.75rem (44px) | The pointer-target floor. Read it wherever you build a target of your own next to a WireKit control — a small icon button, a chip's remove affordance — so the two agree and keep agreeing. Components apply it as a centered ::before over a smaller painted box, which is why a 14px close glyph is still comfortable to hit |
--size-wk-target-min |
1.5rem (24px) | The other floor, and not interchangeable with the one above: this is what WCAG 2.5.8 asks at level AA, where 44px is 2.5.5 at AAA. Reach for this one where a control has two targets that can come close together — the range slider's handles come within half a pixel of each other, and two oversized targets stacked there are worse than two correct ones |
--size-wk-fab |
3.5rem (56px) | The floating action button's own box. It is a token because you cannot lay out AROUND a control whose size you cannot read: a FAB is position: fixed over whatever scrolls beneath it, so that content has to keep the space free at its end. Use wk-fab-clearance on the scrolling container rather than doing the arithmetic yourself |
--size-wk-table-sticky-column-max |
min(40vw, 16rem) | The widest the frozen first column of a sticky-column table gets before its labels wrap. A cap rather than a width: a short label keeps its own width, and a table with room to spare still gives the column its share. Set it on one table with style to change it there |
--size-wk-container-sm |
40rem | Max width of <x-wirekit::container size="sm"> — a reading column |
--size-wk-container-md |
48rem | Container md |
--size-wk-container-lg |
64rem | Container lg |
--size-wk-container-xl |
80rem | Container xl |
--size-wk-container-2xl |
96rem | Container 2xl — the widest step, for dashboards rather than prose |
--padding-wk-x-sm |
0.625rem | Small horizontal padding |
--padding-wk-x-md |
0.75rem | Default horizontal padding |
--padding-wk-x-lg |
1rem | Large horizontal padding |
--padding-wk-x-xl |
1.5rem | Extra-large horizontal padding (overlays) |
--padding-wk-y-xs |
0.25rem | Extra-small vertical padding (tooltip, dropdown) |
--padding-wk-y-sm |
0.375rem | Small vertical padding (textarea) |
--padding-wk-y-md |
0.5rem | Default vertical padding (textarea) |
--padding-wk-y-lg |
0.75rem | Large vertical padding (textarea) |
--padding-wk-y-xl |
1rem | Extra-large vertical padding (overlays) |
--gap-wk-xs |
0.25rem | Extra-small gap (tight groups) |
--gap-wk-sm |
0.5rem | Small gap/spacing (dropdown items) |
--gap-wk-md |
0.75rem | Default gap/spacing (footer buttons) |
--gap-wk-lg |
1rem | Large gap (section groups, sidebars) |
--gap-wk-xl |
1.5rem | Extra-large gap |
--gap-wk-2xl |
2rem | 2x-large gap |
Two spacing ladders, same rung names
Two ladders share the xs…2xl rung labels and they are not the same values.
--gap-wk-* is the tighter one, for space between items inside a component;
--space-wk-* is roomier, for space between blocks of a page. Only xs and sm
happen to agree — reaching for the wrong one gets you 0.75rem where you expected
1rem, and the gap widens as you go up:
| Rung | --gap-wk-* |
--space-wk-* |
|---|---|---|
xs |
0.25rem | 0.25rem |
sm |
0.5rem | 0.5rem |
md |
0.75rem | 1rem |
lg |
1rem | 1.5rem |
xl |
1.5rem | 2.5rem |
2xl |
2rem | 4rem |
Font Sizes
| Variable | Default | Used for |
|---|---|---|
--text-wk-2xs |
0.6875rem | Micro text (dense secondary labels) |
--text-wk-xs |
0.75rem | Caption / micro-label |
--text-wk-sm |
0.8125rem | Small font size |
--text-wk-md |
0.875rem | Default body font size |
--text-wk-lg |
1rem | Large body / lede |
--text-wk-xl |
1.25rem | Sub-heading |
--text-wk-2xl |
1.5rem | Section heading |
--text-wk-3xl |
1.875rem | Page heading |
--text-wk-4xl |
2.25rem | Display heading — a hero or landing headline, above the page-heading step |
--text-wk-5xl |
3rem | The largest step. Reach for it when a headline IS the page; at body-copy sizes below it, the jump reads as a change of register rather than of level |
Every step is calc(<base> * var(--font-scale-wk)), so the whole ladder scales
from one number — see --font-scale-wk below.
| Variable | Default | Used for |
|---|---|---|
--font-scale-wk |
1 | Multiplier on the entire type scale. Set it once — <html style="--font-scale-wk: 1.25"> or in your :root — and every --text-wk-* step grows with it, because each is a calc() against this value. It exists so a reader-facing type-size control changes one number instead of overriding eleven, and so the ratios between steps survive the change. |
--text-wk-touch-floor |
16px | The smallest text a form field shows on a touch screen, because iOS zooms into a field with smaller text the moment it is focused. Every field and the editor read it under pointer: coarse, and so does a button given touch-text-floor, for one that shares a row with a field. A fixed length rather than a step of the ladder: the zoom threshold does not scale with the type, so keep it at 16px or above |
--leading-wk-tight |
1.25 | Line height for headings and other short, large text, where the default 1.5 opens gaps that read as separate lines |
--leading-wk-relaxed |
1.625 | Line height for long-form body copy — the prose register, where a longer measure needs more room between lines to keep the eye on the right one |
--font-wk-heading-xl |
2.5rem | Heading size used by the marketing surfaces (hero, cta) — separate from the --text-wk-* ladder because a landing headline is sized against the section, not against body copy |
--font-wk-heading-2xl |
3.5rem | The larger of the two marketing heading sizes |
--font-wk-heading-line-height |
1.25 | Line height applied to those marketing headings |
--measure-wk |
65ch | Comfortable reading measure — the max line length prose and the reading components hold text to. Around 65 characters is where a line stays scannable without the eye losing its place on the return sweep |
--measure-wk-wide |
78ch | The wider measure, for content that carries code blocks or tables a 65ch column would squeeze |
Overlay Tokens
| Variable | Default (Light) | Default (Dark) | Used for |
|---|---|---|---|
--color-wk-overlay |
oklch(0% 0 0 / 0.5) | oklch(0% 0 0 / 0.7) | Modal/Drawer backdrop |
--z-wk-dropdown |
50 | — | Dropdown z-index |
--z-wk-tooltip |
60 | — | Tooltip z-index |
--z-wk-modal |
70 | — | Modal z-index |
--z-wk-drawer |
70 | — | Drawer z-index |
--z-wk-toast |
80 | — | Toast region z-index. Above the modal on purpose: a toast reports the RESULT of something the reader just did, and half the time they did it inside a dialog — below it, the confirmation of a save is hidden by the form that saved. The ten-point step leaves room for an application to sit between |
--size-wk-modal-sm |
24rem | — | Small modal width |
--size-wk-modal-md |
32rem | — | Default modal width |
--size-wk-modal-lg |
42rem | — | Large modal width |
--size-wk-modal-xl |
56rem | — | Extra-large modal width |
--size-wk-modal-full |
100% | — | Full-width modal |
--size-wk-drawer-sm |
20rem | — | Small drawer width/height |
--size-wk-drawer-md |
28rem | — | Default drawer width/height |
--size-wk-drawer-lg |
40rem | — | Large drawer width/height |
--size-wk-drawer-inset |
3rem | — | The strip of backdrop left showing beside an open drawer. Every width above is wider than a phone, so the panel is clamped against the viewport and this is what stays visible — it is what makes the drawer read as a sheet over the page rather than as a second page. Lower it for a dense console, raise it to show more of the page |
--z-wk-sticky |
40 | — | Sticky page furniture that must YIELD to an overlay — a sticky table head, a status-matrix row head, a floating scroll-to-top. A dropdown opened over a table has to paint on top of it, which is why this sits below --z-wk-dropdown |
--z-wk-chrome |
55 | — | Page chrome that must LEAD — the sticky header and navbar bars. Above --z-wk-dropdown so a popover opened in page content cannot paint over the site header, and below --z-wk-modal so a modal still covers everything. A panel anchored in the header opens downward and never overlaps the bar, so nothing is lost |
--space-wk-toast-offset |
0px | — | Extra inset for the toast region, added on top of its normal edge distance. Defaults to nothing; set it when app furniture (a bottom nav, a cookie bar) would otherwise sit under the toasts |
--wk-scrollbar-inset |
0px | — | Width a classic scrollbar takes out of the viewport box, folded into the inline edge of every viewport-pinned surface. Already correct at zero for overlay scrollbars; an app that renders a classic one measures innerWidth - clientWidth once and sets it |
--wk-sticky-panel-w |
20rem | — | Width of <x-wirekit::sticky-panel>, the companion column beside an article |
--indicator-wk-offset |
0px | — | Pushes an <x-wirekit::indicator> badge further out from its target's corner. Zero means flush; raise it when the badge would otherwise overlap content it is annotating |
--wk-image-compare-handle-size |
2.5rem | — | Diameter of the drag handle on <x-wirekit::image-compare> |
--wk-image-compare-divider-size |
2px | — | Width of the divider line that handle rides on |
--size-wk-tooltip-max |
20rem | — | Tooltip max width |
--color-wk-tooltip-bg |
text color | text color | Tooltip background (inverted) |
--color-wk-tooltip-text |
bg color | bg color | Tooltip text (inverted) |
Application shell
The shell's chrome reads a set of ROLES rather than the generic neutrals, and each tone on
<x-wirekit::app-rail>, <x-wirekit::sidebar> or <x-wirekit::app-shell> re-points that same
set. The table below is the roster — it is the count, so this sentence does not carry one. Override them here to change every tone's fallback at once; override them under a
tone's own selector to change one.
A toned navigation column also re-points the generic tokens (--color-wk-text,
--color-wk-bg-muted, --color-wk-border, …) to these roles for its own subtree. That is how
ordinary components inside a dark rail come out legible without any of them knowing that tones
exist — and it is scoped to the navigation surfaces, never to the shell root, so a toned shell
with a light content panel keeps its content on the normal palette.
| Variable | Default (Light) | Default (Dark) | Used for |
|---|---|---|---|
--size-wk-shell-bar |
3.5rem | 3.5rem | Height of every <x-wirekit::shell-bar>, and therefore the y-coordinate of the horizontal rule across all of a console shell's columns. One token so the segments cannot drift apart |
--size-wk-rail-icon |
1.25rem | 1.25rem | The glyph box a rail entry is built around. It sizes the rail's own width below, and it caps the line box a module name sits in — an entry keeps its height whether or not its name is showing |
--size-wk-rail |
derived | derived | Rail width with labels="tooltip". Computed rather than set: --size-wk-rail-icon plus the small padding on both axes plus one border, which comes to 3.25rem + 1px today. It is a calc() so that re-tuning the padding scale moves the rail with it — matching the sidebar's own collapsed rail, which is derived the same way, so a shell can switch between the two without the content reflowing |
--size-wk-rail-labeled |
5.5rem | 5.5rem | Rail width with labels="below" — room for a caption under the icon |
--size-wk-rail-expanded |
15rem | 15rem | Rail width with labels="inline", and while expanded |
--size-wk-sidebar-drawer |
16rem | 16rem | Width of the app-shell's sidebar as a slide-over, below the breakpoint where it stops being a column. Its own step because neither existing scale reaches it — the drawer widths start at 20rem and the rail widths top out at 15rem |
--wk-nav-pad |
0px | 0px | What a container pads its children by, published so a <x-wirekit::shell-bar bleed> inside it can cancel exactly that much. <x-wirekit::sidebar> sets it; zero everywhere else, so bleed is a no-op outside such a container |
--radius-wk-nav-item |
--radius-wk-md |
--radius-wk-md |
The corner radius of a navigation item — a sidebar row, a rail module. A rounded column re-points it for its own subtree so the two arcs stay CONCENTRIC: an inner corner that is not (outer radius − the gap between them) makes the space between them pinch at 45°. Measured before it was derived: a flat 8px whichever surface the item sat in — nearly right by coincidence in a 16px rail, more than twice too round in a 10px card |
--wk-rail-item-aspect |
auto |
auto |
The shape of a module in the icon-only rail. auto is a pill as tall as its content; 1 makes every module a SQUARE, which is what an app icon is everywhere else. Applied only where nothing shares the box with the glyph — a square with a word under it is not a square |
--radius-wk-shell-panel |
--radius-wk-xl |
--radius-wk-xl |
Corner radius of an inset content panel (<x-wirekit::app-shell panel>) and of <x-wirekit::app-rail variant="panel">. Its own token because the panel is the largest rounded surface on the page, and a theme wanting sharp cards with a soft shell has no way to say so if the two share a value |
--color-wk-rail-bg |
bg-elevated | bg-elevated | Navigation surface |
--color-wk-rail-text |
text | text | Navigation foreground |
--color-wk-rail-muted |
text-muted | text-muted | Resting item foreground |
--color-wk-rail-hover-bg |
bg-muted | bg-muted | Item hover surface |
--color-wk-rail-active-bg |
bg-muted | bg-muted | Current item's fill, with indicator="pill" |
--color-wk-rail-active-text |
text | text | Current item's foreground on the COLUMN, and the indicator="edge" bar |
--color-wk-rail-hover-fg |
text | text | Foreground on a hover FILL. The same color as the one above on a neutral surface, and deliberately not on a colored one |
--color-wk-rail-active-fg |
text | text | Foreground on the current item's FILL |
--color-wk-rail-border |
border | border | Column edges, group separators, and the shell rule |
--color-wk-rail-ring |
ring | ring | Focus ring inside the column. Its own role because a ring tuned for the page background can fall below the 3:1 contrast floor on a dark rail |
--color-wk-rail-badge |
accent | accent | The counter dot on a module, and the pill it becomes in a wide rail. Its own role because the rail is the one surface whose background is a tone: on a dark column the page accent is near-black on near-black, and the mark whose whole job is to say "there is something here" is the one that disappears |
--color-wk-rail-badge-fg |
accent-fg | accent-fg | The digits carried on that pill |
The tones
| Tone | Surface | Notes |
|---|---|---|
default |
--color-wk-bg-elevated |
The roles above, unchanged |
muted |
--color-wk-bg-muted |
Hover and active are mixes of the tone's own text, so the step is visible in both light and dark; a fixed lighter/darker token only works in one of them |
inverse |
oklch(20.5% 0 0) |
Deliberately mode-INDEPENDENT, and there is no dark override. --color-wk-bg-inverse cannot be used here because it flips to near-white under .dark, which would turn an "inverse" rail white on a dark page. 20.5% is also dark mode's own elevated surface, so one value is correct in both modes rather than two that must be kept in step. Measured: white on it is ~14.9:1 and the muted step ~7.5:1 |
accent |
--color-wk-accent |
Every role that carries text is a plain alias of --color-wk-accent or --color-wk-accent-fg, with no mix. Muted text equals the full foreground, and hover and the current item INVERT — the fill becomes the foreground color and the label becomes the accent. That is the guaranteed pair read backwards, so the tone's contrast is the accent pair's, which is already verified for every bundled preset in both modes. Tinting was tried in both directions first and neither is safe: a mix toward the foreground measured 1.73:1 to 4.17:1 across five presets, and a mix the other way still failed on one, because the percentage depends on an accent each theme defines |
Scrollbar
| Variable | Default (Light) | Default (Dark) | Used for |
|---|---|---|---|
--color-wk-scrollbar-thumb |
neutral-300 | neutral-600 | Scrollbar thumb color |
--color-wk-scrollbar-thumb-hover |
neutral-400 | neutral-500 | Scrollbar thumb on hover |
--color-wk-scrollbar-track |
transparent | transparent | Scrollbar track background |
--size-wk-scrollbar |
6px | — | Scrollbar width/height |
--fade-wk-size |
2rem | — | Depth of the scroll-area edge fade (<x-wirekit::scroll-area fade="…"> / .wk-scroll-fade) |
Apply the .wk-scrollbar CSS class to any scrollable element for a slim, themed scrollbar:
<div class="wk-scrollbar overflow-auto max-h-64">
<!-- long content here -->
</div>
The class uses scrollbar-width: thin for Firefox and ::-webkit-scrollbar pseudo-elements for Chrome/Safari/Edge.
Reading Component Tokens
Tokens for the reading-* component family — six primitives for long-form content pages. Override any of them in your :root {} block to retheme without touching the component source.
Reading Progress
| Variable | Default | Used for |
|---|---|---|
--reading-progress-height-sm |
2px |
Bar height when height="sm" |
--reading-progress-height-md |
3px |
Bar height when height="md" (default) |
--reading-progress-height-lg |
5px |
Bar height when height="lg" |
--reading-progress-dot-size |
2.5rem |
Diameter of the dot indicator (when indicator="dot") |
--reading-progress-fill |
(unset) | Developer-side fill override — set to win over the variant-resolved color for theme-wide retheming |
--reading-progress-segment |
(unset) | Color of the chapter dividers the segments prop paints. Unset they follow --color-wk-border-strong, so they flip with the theme; set it to retint them alongside --reading-progress-fill |
Variant rendering: each variant resolves through var(--reading-progress-fill, var(--color-wk-X)), so the --reading-progress-fill token wins when set. Default variants pull from --color-wk-accent (primary, info), --color-wk-text-muted (neutral), --color-wk-success / --color-wk-warning / --color-wk-danger (semantic). The auto variant falls back to currentColor for embedded contexts.
Reading Spine
| Variable | Default | Used for |
|---|---|---|
--reading-spine-width-collapsed |
1.5rem |
Width of the collapsed (ticks-only) spine |
--reading-spine-width-expanded |
16rem |
Width when expanded on hover/focus |
--reading-spine-gap |
0.125rem |
Vertical gap between ticks |
--reading-spine-tick-height |
2px |
Tick line thickness |
--reading-spine-edge-offset |
1rem |
Distance from the viewport edge position chose. Set it here to move every spine in an application, or per call site with the edge-offset prop; 0 sits the spine flush against the window |
--reading-spine-color-idle |
var(--color-wk-text-subtle) |
Inactive tick / label color |
--reading-spine-color-active |
var(--color-wk-text) |
Active tick / label color |
--reading-spine-bg |
color-mix(in srgb, var(--color-wk-bg-elevated) 92%, transparent) |
Spine panel background |
--reading-spine-padding-x |
0.5rem |
Inline padding of the spine panel |
--reading-spine-padding-y |
0.875rem |
Block padding of the spine panel |
--reading-spine-list-padding-x |
= spine-padding-x |
Inline padding of the tick list inside the panel. Its own knob so the list can be inset further than the panel without moving the panel's edge |
--reading-spine-list-padding-y |
= spine-padding-y |
Block padding of that list |
--reading-spine-offset-top |
1rem |
Distance from the top of the sticky viewport to the spine |
--reading-content-spine-gutter |
12rem |
Space the article reserves beside itself for the spine. It is the article's gutter, not the spine's width — the spine expands on hover, and this is what keeps that expansion from landing on the text |
--reading-spine-edge-gradient |
elevated-bg fade | The mask that dissolves the spine's inner edge into the page. A gradient rather than a solid so the panel has no hard seam against the article at any background |
Reading Minimap
| Variable | Default | Used for |
|---|---|---|
--reading-minimap-width |
60px |
Column width |
--reading-minimap-stripe-height |
2px |
Stripe height per item |
--reading-minimap-stripe-gap |
1px |
Vertical gap between stripes |
--reading-minimap-color-idle |
var(--reading-spine-color-idle) |
Inactive stripe color (reuses spine color for theme-consistency) |
--reading-minimap-color-active |
var(--reading-spine-color-active) |
Active stripe color (item currently in viewport) |
--reading-minimap-viewport-bg |
color-mix(in oklab, var(--color-wk-accent) 25%, transparent) |
Viewport-overlay background |
--reading-minimap-viewport-border |
2px solid color-mix(in oklab, var(--color-wk-accent) 70%, transparent) |
Viewport-overlay border |
--reading-minimap-viewport-radius |
var(--radius-wk-sm) |
Viewport-overlay corner radius |
--reading-minimap-block-bg |
tinted from the idle color | Fill of one block in the block-texture mode |
--reading-minimap-block-gap |
2px |
Vertical gap between those blocks |
--reading-minimap-block-radius |
2px |
Corner radius of a block |
--reading-minimap-anchor-font-size |
0.625rem |
Size of a section anchor label beside the map |
--reading-minimap-anchor-text-color |
var(--color-wk-text-muted) |
Resting color of that label |
--reading-minimap-anchor-text-color-hover |
var(--color-wk-text) |
Its hover color |
--reading-minimap-anchor-inboard-bg |
var(--color-wk-bg) |
Backdrop of a label that moved inside because the outboard edge had no room. That label lies on the article, so the backdrop covers the text beneath instead of letting the two cross |
--reading-minimap-anchor-inboard-padding |
0 var(--padding-wk-x-xs) |
Inline room between that backdrop's edge and its label |
--reading-minimap-anchor-inboard-radius |
var(--radius-wk-sm) |
Corner radius of that backdrop |
--reading-minimap-bookmark-marker-color |
var(--color-wk-accent) |
The marker drawn at a bookmarked position |
--reading-minimap-bookmark-marker-height |
2px |
Its thickness |
--reading-minimap-idle-delay |
3s |
How long the map waits, untouched, before fading toward its idle opacity |
--reading-minimap-idle-opacity |
0.3 |
The opacity it fades to. Never 0 by default: a map that disappears entirely cannot be found again by a reader who did not know it was there |
--reading-minimap-idle-transition |
250ms |
Duration of that fade, in both directions |
--reading-minimap-preview-size |
200px |
Width of the hover preview popped out from the map |
--reading-minimap-preview-scale |
0.25 |
Scale the previewed content is drawn at inside it |
--reading-minimap-preview-bg |
var(--color-wk-bg-elevated) |
Preview background |
--reading-minimap-preview-border |
1px solid var(--color-wk-border) |
Preview border |
--reading-minimap-preview-shadow |
var(--shadow-wk-md) |
Preview elevation |
--reading-minimap-rendered-placeholder-bg |
var(--color-wk-bg-elevated) |
Backing color shown in mode="rendered" before the canvas has painted |
--reading-minimap-rendered-radius |
var(--radius-wk-sm) |
Corner radius of that rendered canvas |
Rendered-mode canvas colors (mode="rendered" only)
The rendered-mode minimap paints an abstract content-texture map onto a canvas — one rectangle per rendered text line, colored by the source element's role. Override any of these tokens in your :root {} and .dark {} blocks to retheme the canvas without touching JS.
These thirteen are the only reading tokens that carry a raw color literal rather than resolving through a --color-wk-* role, so unlike the rest of the family they do not flip with the theme on their own — each ships an explicit light and dark value. If you override one, override both: a :root-only override leaves your dark canvas painting the light color, and a canvas whose marks match its background still renders, it just shows no hierarchy. The alpha is the density encoding, so keep it the same across the pair and change only the hue.
| Variable | Default (Light) | Default (Dark) | Used for |
|---|---|---|---|
--reading-minimap-color-h1 |
rgba(15, 23, 42, 0.85) |
rgba(226, 232, 240, 0.85) |
h1 lines (densest — top of structural hierarchy) |
--reading-minimap-color-h2 |
rgba(15, 23, 42, 0.75) |
rgba(226, 232, 240, 0.75) |
h2 lines |
--reading-minimap-color-h3 |
rgba(15, 23, 42, 0.65) |
rgba(226, 232, 240, 0.65) |
h3 lines |
--reading-minimap-color-h4 |
rgba(15, 23, 42, 0.55) |
rgba(226, 232, 240, 0.55) |
h4 lines |
--reading-minimap-color-h5 |
rgba(15, 23, 42, 0.48) |
rgba(226, 232, 240, 0.48) |
h5 lines |
--reading-minimap-color-h6 |
rgba(15, 23, 42, 0.42) |
rgba(226, 232, 240, 0.42) |
h6 lines |
--reading-minimap-color-code |
rgba(99, 102, 241, 0.55) |
rgba(129, 140, 248, 0.55) |
<pre>, <code>, <kbd>, <samp> |
--reading-minimap-color-table |
rgba(244, 63, 94, 0.50) |
rgba(251, 113, 133, 0.50) |
<table>, <td>, <th>, <caption> |
--reading-minimap-color-blockquote |
rgba(71, 85, 105, 0.55) |
rgba(148, 163, 184, 0.55) |
<blockquote> |
--reading-minimap-color-prose |
rgba(71, 85, 105, 0.42) |
rgba(148, 163, 184, 0.42) |
<p>, <li>, <dd>, <dt>, <figcaption> |
--reading-minimap-color-wirekit |
rgba(16, 185, 129, 0.50) |
rgba(52, 211, 153, 0.50) |
Any WireKit component ([class*="wk-"], excluding the minimap's own wk-reading-* siblings) |
--reading-minimap-color-image |
rgba(245, 158, 11, 0.55) |
rgba(251, 191, 36, 0.55) |
<img>, <picture>, <figure>, <svg>, <video>, <canvas> (drawn as block rectangles at natural element bounds) |
--reading-minimap-color-default |
rgba(71, 85, 105, 0.32) |
rgba(148, 163, 184, 0.32) |
Anything else (fallback) |
Reading TOC
| Variable | Default | Used for |
|---|---|---|
--reading-toc-bg |
var(--color-wk-bg-elevated) |
Strip background. It was documented here as a 92% color-mix — the sibling --reading-spine-bg is one, and this is not; the strip is opaque |
--reading-toc-color-idle |
var(--reading-spine-color-idle) |
Inactive link color (reuses spine color for theme-consistency) |
--reading-toc-color-active |
var(--reading-spine-color-active) |
Active link color (the section the reader is in) |
--reading-toc-padding-y |
0.5rem |
Vertical padding inside the strip |
--reading-toc-padding-x |
1rem |
Horizontal padding inside the strip |
--reading-toc-gap |
1rem |
Gap between adjacent links |
--reading-toc-link-max-width |
24ch |
Per-link max-width — long heading text gets ellipsis-truncated rather than overflowing the strip |
Reading Bookmark
| Variable | Default | Used for |
|---|---|---|
--reading-bookmark-pill-bg |
var(--color-wk-bg-elevated) |
"Resume reading?" pill background |
--reading-bookmark-pill-radius |
var(--radius-wk-full, 9999px) |
Pill border radius |
--reading-bookmark-pill-shadow |
var(--shadow-wk-md) |
Pill drop shadow |
Reading Meta
| Variable | Default | Used for |
|---|---|---|
--reading-meta-text-size |
0.875rem |
Time-to-read text size |
--reading-meta-color |
var(--color-wk-text-muted) |
Time-to-read text color |
--reading-meta-paragraph-color |
var(--color-wk-text-subtle) |
Per-paragraph annotation color (perParagraph mode) |
--reading-meta-paragraph-spacing |
0.25em |
Gap between annotation and the paragraph below it (perParagraph mode) |
Reading Skeleton
| Variable | Default | Used for |
|---|---|---|
--reading-skeleton-pulse-opacity |
0.5 |
Opacity the reading placeholder pulses down to while content loads. It is the floor of the pulse, not its resting value, so a lower number reads as a stronger heartbeat. Inert under prefers-reduced-motion — see below |
Reduced Motion
All reading-* components honor prefers-reduced-motion: reduce via the global @media block in dist/wirekit.css. Every transition (bar fill transform: scaleX, dot stroke dashoffset, spine width / label opacity / tick color, bookmark pill enter/leave, minimap stripe color transitions, smooth-scroll on link click) collapses to 1ms — values still update on scroll, just without easing.
The system preference is not the only way in. An application that offers the choice in its own settings puts data-reduce-motion="reduce" (or "no-preference") on <html> and these same rules follow it — see the Integration guide for reading it off the signed-in reader, and Theming for the rest of the motion surface.
Print Stylesheet
All five UI primitives (reading-progress, reading-spine, reading-minimap, reading-bookmark, reading-meta) carry a @media print { display: none !important } rule scoped via marker classes (.wk-reading-progress, .wk-reading-spine, .wk-reading-minimap, .wk-reading-bookmark, .wk-reading-meta). Print-friendly for blog-to-PDF workflows — only the article body prints, not the reading chrome.
Chart Theming
When charts are enabled, the <x-wirekit-chart> component automatically reads WireKit CSS variables for consistent theming:
| Variable | Chart Usage |
|---|---|
--color-wk-accent |
Primary dataset color |
--color-wk-danger |
Second dataset color |
--color-wk-success |
Third dataset color |
--color-wk-warning |
Fourth dataset color |
--color-wk-info |
Fifth dataset color — a fixed cyan (#0891b2 light, #a5f3fc dark) rather than an alias, so it does not follow your accent. Override it in your @theme block to re-tone it. |
--color-wk-text |
Legend text color |
--color-wk-text-muted |
Axis tick labels |
--color-wk-border |
Grid line color |
--font-wk-sans |
Chart font family |
Overriding these tokens in your @theme block automatically updates chart colors. See the Chart documentation for details.
See Also
- Theming — setup guide, theme presets, accent-color, accessibility guarantees
- Customization — when to override tokens vs. tweak PHP defaults vs. apply scope-based personalization
- Variants & Intents — cross-component reference for color/severity props