Skip to content

Tokens

About 30 custom properties in @layer tokens — the only place visual decisions live. Replacing this layer is the entire theming story.

Overriding

Paste your own tokens after the keel link tag, inside the same @layer tokens — later declarations in the same layer win, so anything you leave out keeps its default.

<link rel="stylesheet" href="../keel.css">
<style>
  @layer tokens {
    :root {
      --k-accent: hsl(160 84% 32%);
      --k-radius: 4px;
      --k-density: 0.9;
    }
  }
</style>

You don't have to write a full set by hand — the design system page generates one from a color, a mood, and a font pairing.

Color relationships

The accent shades derive from their seed via color-mix(): override --k-accent alone and strong, light, soft, and both transparencies re-derive automatically. The one exception is --k-on-accent, which stays explicit — contrast is a judgment the math can't make safely. The deeper story is on the concepts page.

@layer tokens {
  :root { --k-accent: hsl(160 84% 32%); --k-on-accent: hsl(0 0% 100%); }
}

Auto dark

Opt-in: put data-k-auto-dark on the html element and, when the visitor's OS prefers dark, the neutral seeds flip to a night set — every derived shade follows. It's a sensible default, not a designed theme; sites that care about their dark mode should ship their own tokens block instead (the design page's Night and Dusk moods are a starting point). keel also ships quiet print styles: chrome and overlays hidden, bands flattened to ink on white.

<html lang="en" data-k-auto-dark>

Flat skin

Opt-in: put data-k-flat on the html element to strip elevation — the shadow scale collapses to none and the second surface step is redrawn from the border, so cards, buttons and popovers read as thin, borders-only shapes. It's a separate axis from color, so it composes with any mood or with auto dark; pair it with a smaller --k-radius and a denser scale for the full range from designed to bare.

<html lang="en" data-k-flat>

Color

The swatches are live — each cell paints with its own token, so this reference re-renders in your palette.

TokenWhat it doesDefault
--k-bg Page background. hsl(228 20% 98.5%)
--k-surface Raised surfaces: cards, alt bands, inputs, dialogs. hsl(0 0% 100%)
--k-surface-2 Second surface step: code blocks, table stripes, progress tracks. hsl(228 18% 95%)
--k-border Default border for cards, tables, rules, band edges. hsl(228 14% 87%)
--k-border-strong Stronger border where the edge must read: inputs, ghost buttons. hsl(228 14% 76%)
--k-text Body text color. hsl(228 22% 14%)
--k-text-soft Secondary text: leads, descriptions, table headers. hsl(228 12% 36%)
--k-text-faint Tertiary text: captions, hints, placeholders, footers. hsl(228 9% 52%)
--k-accent The brand color: links, buttons, focus rings, selection. hsl(231 84% 56%)
--k-accent-strong Darker accent for hover states and small text on light surfaces. Derives from the seed. color-mix(in oklab, var(--k-accent), black 20%)
--k-accent-light Lighter accent for accents on dark surfaces. Derives from the seed. color-mix(in oklab, var(--k-accent), white 25%)
--k-on-accent Text placed on the accent: button labels, accent bands. hsl(0 0% 100%)
--k-accent-soft Pale accent wash: tint bands, badges, marks. Derives from the seed, mixed into the page background. color-mix(in oklab, var(--k-accent) 12%, var(--k-bg))
--k-accent-trans-10 Accent at 10% opacity. Genuinely transparent — the swatch shows the page behind it. color-mix(in srgb, var(--k-accent) 10%, transparent)
--k-accent-trans-20 Accent at 20% opacity: focus ring shadows. Transparent, like the 10% step. color-mix(in srgb, var(--k-accent) 20%, transparent)
--k-accent-2 The secondary brand color: secondary buttons and badges. Derived from the primary by harmony on the design page, or set exactly. hsl(351 72% 44%)
--k-accent-2-strong Darker secondary for hover states. Derives from the seed. color-mix(in oklab, var(--k-accent-2), black 20%)
--k-accent-2-light Lighter secondary for dark surfaces. Derives from the seed. color-mix(in oklab, var(--k-accent-2), white 25%)
--k-on-accent-2 Text placed on the secondary. hsl(0 0% 100%)
--k-accent-2-soft Pale secondary wash: secondary badges and .k-section--tint-2 bands. Derives from the seed, mixed into the page background. color-mix(in oklab, var(--k-accent-2) 12%, var(--k-bg))
--k-accent-2-trans-10 Secondary at 10% opacity. Genuinely transparent — the swatch shows the page behind it. color-mix(in srgb, var(--k-accent-2) 10%, transparent)
--k-accent-2-trans-20 Secondary at 20% opacity. Transparent, like the 10% step. color-mix(in srgb, var(--k-accent-2) 20%, transparent)
--k-accent-3 The tertiary/accent brand color: tertiary buttons and badges, highlights. Set exactly on the design page. Carries the same -strong, -light, -soft, and -trans-10/20 variants as the others. hsl(174 70% 28%)
--k-on-accent-3 Text placed on the accent. hsl(0 0% 100%)
--k-accent-3-soft Pale accent wash: tertiary badges and .k-section--tint-3 bands. Derives from the seed, mixed into the page background. color-mix(in oklab, var(--k-accent-3) 12%, var(--k-bg))
--k-ok Status: success. Used by ok badges and alerts. hsl(152 60% 30%)
--k-warn Status: caution. Used by warn badges and alerts. hsl(38 92% 34%)
--k-danger Status: error. Used by danger badges and alerts. hsl(4 74% 42%)

Type

The scale is fluid — each step is a clamp() that breathes with the viewport, no breakpoints.

TokenWhat it doesDefault
--k-font-display Headings and stat values. system-ui, "Segoe UI", sans-serif
--k-font-body Everything else. system-ui, "Segoe UI", sans-serif
--k-font-mono Code, kbd, kickers, eyebrows, the brand mark. ui-monospace, Consolas, "Cascadia Mono", monospace
--k-display-weight Weight of headings. Fonts with variable weight can go anywhere between 400 and 900. 650
--k-tracking Letter-spacing of headings. -0.015em
--k-leading Body line-height. 1.65
--k-leading-tight Heading line-height. 1.12
--k-text-xs Smallest step: badges, hints, table headers, labels. clamp(0.78rem, 0.75rem + 0.15vw, 0.84rem)
--k-text-s Small step: code blocks, tables, small print, nav links. clamp(0.88rem, 0.85rem + 0.18vw, 0.95rem)
--k-text-m Body size. clamp(1rem, 0.96rem + 0.22vw, 1.1rem)
--k-text-l Large step: h4, leads, blockquotes. clamp(1.15rem, 1.06rem + 0.45vw, 1.35rem)
--k-text-xl h3. clamp(1.4rem, 1.22rem + 0.9vw, 1.8rem)
--k-text-2xl h2 and stat values. clamp(1.8rem, 1.48rem + 1.6vw, 2.5rem)
--k-text-3xl h1. clamp(2.4rem, 1.9rem + 2.6vw, 3.6rem)

Space

Every step multiplies by --k-density — one number loosens or tightens the whole page; steps 1–4 are static, 5–8 fluid.

TokenWhat it doesDefault
--k-density Global spacing multiplier. 0.85 is compact, 1.15 is airy. 1
--k-space-1 Hairline gaps: list items, hints, labels. calc(0.25rem * var(--k-density))
--k-space-2 Tight padding: table cells, input blocks, badges. calc(0.5rem * var(--k-density))
--k-space-3 Small gaps: clusters, heading margins, alerts. calc(0.75rem * var(--k-density))
--k-space-4 The base rhythm: paragraph margins, stack gaps, card grids. calc(1rem * var(--k-density))
--k-space-5 Card padding, split gaps, blockquote margins. Fluid. calc(clamp(1.4rem, 1.2rem + 0.9vw, 2rem) * var(--k-density))
--k-space-6 Loose stacks, tight bands, horizontal rules, footers. Fluid. calc(clamp(2.25rem, 1.85rem + 1.8vw, 3.4rem) * var(--k-density))
--k-space-7 Section band padding. Fluid. calc(clamp(3.25rem, 2.5rem + 3.5vw, 5.5rem) * var(--k-density))
--k-space-8 Hero band padding. Fluid. calc(clamp(4.5rem, 3.3rem + 5.5vw, 8rem) * var(--k-density))

Shape

One knob: --k-radius — the small and large steps derive from it, and 0 makes the whole framework square.

TokenWhat it doesDefault
--k-radius Base corner radius: buttons, code blocks, alerts, images, fieldsets. 8px
--k-radius-s Small step, derived: inline code, inputs, pagination. calc(var(--k-radius) * 0.5)
--k-radius-l Large step, derived: cards, dialogs. calc(var(--k-radius) * 1.75)

Width

TokenWhat it doesDefault
--k-width-narrow Prose measure. Used by .k-container--narrow. 46rem
--k-width Default container width. Used by .k-container. 72rem
--k-width-wide Wide container for grids and tables. Used by .k-container--wide. 90rem
--k-gutter Fluid side margin. Drives the margin-based .k-container--gutter — no width cap, just breathing room. clamp(1.25rem, 5vw, 5rem)

Other

TokenWhat it doesDefault
--k-shadow-s Subtle edge shadow for small controls. 0 1px 2px hsl(228 35% 12% / 0.05)
--k-shadow The default elevation: cards and dialogs. 0 1px 2px hsl(228 35% 12% / 0.04), 0 6px 18px -8px hsl(228 35% 12% / 0.10)
--k-shadow-l High elevation: dropdown menus, lifted cards. 0 2px 6px hsl(228 35% 12% / 0.05), 0 22px 44px -18px hsl(228 35% 12% / 0.20)
--k-ease Duration and easing for every transition. 160ms ease

A note on stability: keel is pre-1.0. Token names may still tighten before 1.0 — changes land in the changelog. Treat this list as the current proposal, not a contract.