Foundations

Tokens are the public API. Components churn; token names should not. Everything below is what a consuming app is allowed to depend on - including the theme, which is nothing more than the semantic tier redefined.

Reference

the theme

Light and dark, and no component rule knows about either. The whole theme is one file redefining the semantic tier, written with light-dark() so both values sit side by side in a single declaration rather than in a second palette under [data-theme] and a third under a prefers-color-scheme media query, drifting apart from the first edit. The switch is color-scheme, which is the property the browser already uses to paint scrollbars, form controls and the canvas behind the body - a class that sets the palette without setting color-scheme is how a page ends up dark with a white scrollbar.

<html>                       <!-- follows the operating system -->
<html data-theme="light">     <!-- pinned light -->
<html data-theme="dark">      <!-- pinned dark -->

<section data-theme="dark">   <!-- one subtree, either way -->
AttributeTypeDefaultNotes
data-theme"light" | "dark"systemWorks on any element, not just the root: light-dark() resolves against the element's own used color-scheme, so a dark hero band on a light page is one attribute.
@supportslight-dark()-The whole theme file is guarded. A browser without light-dark() skips it and keeps the light palette this system shipped with for three versions - the right failure for a page that would otherwise have no colours at all.
the switcher--The three links at the bottom of this sidebar are ordinary links carrying ?theme=. Rendering the choice server-side is not a workaround for the no-JavaScript rule: a script-driven toggle cannot run before the first paint, which is what causes the flash of the wrong theme on every page load.
what it may not do--Touch a component selector. If a dark value cannot be reached by redefining a token, the component is reaching past the semantic tier - which happened four times while the theme was written, and each one was a latent bug rather than a theming inconvenience.

sk-mark

The marker-pen swash. Drawn as an inline background rather than a positioned pseudo-element, so it wraps across lines and scales with the text it marks.

<h2>Our <span class="sk-mark">games</span></h2>
AttributeTypeDefaultNotes
data-fill"true"underline strokeFills the whole line box instead of drawing a stroke along the baseline.
--sk-mark-heightlength (em)0.4emStroke thickness, in em so it holds its proportion at every font size.
--sk-mark-offsetlength (em)0.03emClearance below the stroke.

Examples

Colour

kiwi-400 has roughly 1.6:1 contrast against white. It is a fill colour only - never text, never a 1px border on a light surface. Anything sitting on it uses ink (9.4:1).

Brand

kiwi-400 and ink-950 are anchors taken from softkiwi.games and must not drift. The rest of the ramp is derived in OKLCH.

--sk-kiwi-100
--sk-kiwi-200
--sk-kiwi-300
--sk-kiwi-400 anchor - bg-kiwi-400
--sk-kiwi-500
--sk-kiwi-600
--sk-kiwi-700
--sk-kiwi-800
--sk-ink-950 anchor - emerald-950

Neutrals

Tinted toward the brand hue so neutral surfaces sit next to kiwi without looking dirty.

--sk-neutral-0
--sk-neutral-50
--sk-neutral-100
--sk-neutral-200
--sk-neutral-300
--sk-neutral-400
--sk-neutral-500
--sk-neutral-600 4.9:1 on white
--sk-neutral-700
--sk-neutral-800
--sk-neutral-900

Semantic

The only tier components reference. Theming redefines these and nothing else - which is why the dark theme touches no component rule.

--sk-surface
--sk-surface-sunken
--sk-surface-raised
--sk-ink
--sk-ink-muted
--sk-ink-subtle
--sk-border
--sk-border-strong
--sk-action-bg
--sk-action-bg-hover
--sk-action-fg
--sk-accent
--sk-accent-soft
--sk-danger-fg
--sk-danger-bg
--sk-danger-solid-bg
Show markup
<div class="sk-stack" data-gap="6">
  <div class="sk-stack" data-gap="3">
    <h3>
      Brand
    </h3>
    <p class="sink-story-note">
      kiwi-400 and ink-950 are anchors taken from softkiwi.games and must not drift. The rest of the ramp is derived in OKLCH.
    </p>
    <div class="sink-grid">
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-100">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-100
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-200">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-200
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-300">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-300
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-400">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-400
        </span>
        <span class="sink-swatch-note">
          anchor - bg-kiwi-400
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-500">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-500
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-600">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-600
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-700">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-700
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="kiwi-800">
        </div>
        <span class="sink-swatch-name">
          --sk-kiwi-800
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="ink-950">
        </div>
        <span class="sink-swatch-name">
          --sk-ink-950
        </span>
        <span class="sink-swatch-note">
          anchor - emerald-950
        </span>
      </div>
    </div>
  </div>
  <div class="sk-stack" data-gap="3">
    <h3>
      Neutrals
    </h3>
    <p class="sink-story-note">
      Tinted toward the brand hue so neutral surfaces sit next to kiwi without looking dirty.
    </p>
    <div class="sink-grid">
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-0">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-0
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-50">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-50
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-100">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-100
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-200">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-200
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-300">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-300
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-400">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-400
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-500">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-500
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-600">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-600
        </span>
        <span class="sink-swatch-note">
          4.9:1 on white
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-700">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-700
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-800">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-800
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="neutral-900">
        </div>
        <span class="sink-swatch-name">
          --sk-neutral-900
        </span>
      </div>
    </div>
  </div>
  <div class="sk-stack" data-gap="3">
    <h3>
      Semantic
    </h3>
    <p class="sink-story-note">
      The only tier components reference. Theming redefines these and nothing else - which is why the dark theme touches no component rule.
    </p>
    <div class="sink-grid">
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="surface">
        </div>
        <span class="sink-swatch-name">
          --sk-surface
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="surface-sunken">
        </div>
        <span class="sink-swatch-name">
          --sk-surface-sunken
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="surface-raised">
        </div>
        <span class="sink-swatch-name">
          --sk-surface-raised
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="ink">
        </div>
        <span class="sink-swatch-name">
          --sk-ink
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="ink-muted">
        </div>
        <span class="sink-swatch-name">
          --sk-ink-muted
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="ink-subtle">
        </div>
        <span class="sink-swatch-name">
          --sk-ink-subtle
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="border">
        </div>
        <span class="sink-swatch-name">
          --sk-border
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="border-strong">
        </div>
        <span class="sink-swatch-name">
          --sk-border-strong
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="action-bg">
        </div>
        <span class="sink-swatch-name">
          --sk-action-bg
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="action-bg-hover">
        </div>
        <span class="sink-swatch-name">
          --sk-action-bg-hover
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="action-fg">
        </div>
        <span class="sink-swatch-name">
          --sk-action-fg
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="accent">
        </div>
        <span class="sink-swatch-name">
          --sk-accent
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="accent-soft">
        </div>
        <span class="sink-swatch-name">
          --sk-accent-soft
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="danger-fg">
        </div>
        <span class="sink-swatch-name">
          --sk-danger-fg
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="danger-bg">
        </div>
        <span class="sink-swatch-name">
          --sk-danger-bg
        </span>
      </div>
      <div class="sink-swatch">
        <div class="sink-swatch-chip" data-token="danger-solid-bg">
        </div>
        <span class="sink-swatch-name">
          --sk-danger-solid-bg
        </span>
      </div>
    </div>
  </div>
</div>

Type scale

Open Sans, self-hosted as a variable font. Two woff2 files cover every weight from 300 to 800.

--sk-text-xs The quick brown fox 0.75rem
--sk-text-sm The quick brown fox 0.875rem
--sk-text-md The quick brown fox 1rem
--sk-text-lg The quick brown fox 1.125rem
--sk-text-xl The quick brown fox 1.25rem
--sk-text-2xl The quick brown fox 1.5rem
--sk-text-3xl The quick brown fox 1.875rem
--sk-text-4xl The quick brown fox 2.25rem
Show markup
<div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-xs
    </span>
    <span class="sink-type" data-token="text-xs">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      0.75rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-sm
    </span>
    <span class="sink-type" data-token="text-sm">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      0.875rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-md
    </span>
    <span class="sink-type" data-token="text-md">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      1rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-lg
    </span>
    <span class="sink-type" data-token="text-lg">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      1.125rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-xl
    </span>
    <span class="sink-type" data-token="text-xl">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      1.25rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-2xl
    </span>
    <span class="sink-type" data-token="text-2xl">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      1.5rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-3xl
    </span>
    <span class="sink-type" data-token="text-3xl">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      1.875rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-text-4xl
    </span>
    <span class="sink-type" data-token="text-4xl">
      The quick brown fox
    </span>
    <span class="sink-swatch-note">
      2.25rem
    </span>
  </div>
</div>

Space scale

4px base, eight steps, no half-steps.

--sk-space-1 0.25rem
--sk-space-2 0.5rem
--sk-space-3 0.75rem
--sk-space-4 1rem
--sk-space-5 1.5rem
--sk-space-6 2rem
--sk-space-7 3rem
--sk-space-8 4rem
Show markup
<div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-1
    </span>
    <span class="sink-bar" data-token="space-1">
    </span>
    <span class="sink-swatch-note">
      0.25rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-2
    </span>
    <span class="sink-bar" data-token="space-2">
    </span>
    <span class="sink-swatch-note">
      0.5rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-3
    </span>
    <span class="sink-bar" data-token="space-3">
    </span>
    <span class="sink-swatch-note">
      0.75rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-4
    </span>
    <span class="sink-bar" data-token="space-4">
    </span>
    <span class="sink-swatch-note">
      1rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-5
    </span>
    <span class="sink-bar" data-token="space-5">
    </span>
    <span class="sink-swatch-note">
      1.5rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-6
    </span>
    <span class="sink-bar" data-token="space-6">
    </span>
    <span class="sink-swatch-note">
      2rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-7
    </span>
    <span class="sink-bar" data-token="space-7">
    </span>
    <span class="sink-swatch-note">
      3rem
    </span>
  </div>
  <div class="sink-row">
    <span class="sink-row-label">
      --sk-space-8
    </span>
    <span class="sink-bar" data-token="space-8">
    </span>
    <span class="sink-swatch-note">
      4rem
    </span>
  </div>
</div>

Radius

--sk-radius-sm 0.25rem
--sk-radius-md 0.375rem - rounded-md
--sk-radius-lg 0.5rem - rounded-lg
--sk-radius-full pills
Show markup
<div class="sk-cluster" data-gap="4">
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-radius" data-token="radius-sm">
    </div>
    <span class="sink-swatch-name">
      --sk-radius-sm
    </span>
    <span class="sink-swatch-note">
      0.25rem
    </span>
  </div>
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-radius" data-token="radius-md">
    </div>
    <span class="sink-swatch-name">
      --sk-radius-md
    </span>
    <span class="sink-swatch-note">
      0.375rem - rounded-md
    </span>
  </div>
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-radius" data-token="radius-lg">
    </div>
    <span class="sink-swatch-name">
      --sk-radius-lg
    </span>
    <span class="sink-swatch-note">
      0.5rem - rounded-lg
    </span>
  </div>
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-radius" data-token="radius-full">
    </div>
    <span class="sink-swatch-name">
      --sk-radius-full
    </span>
    <span class="sink-swatch-note">
      pills
    </span>
  </div>
</div>

Elevation

--sk-shadow-sm
--sk-shadow-md
--sk-shadow-lg
Show markup
<div class="sk-cluster" data-gap="5">
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-shadow" data-token="shadow-sm">
    </div>
    <span class="sink-swatch-name">
      --sk-shadow-sm
    </span>
  </div>
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-shadow" data-token="shadow-md">
    </div>
    <span class="sink-swatch-name">
      --sk-shadow-md
    </span>
  </div>
  <div class="sink-swatch">
    <div class="sink-swatch-chip sink-shadow" data-token="shadow-lg">
    </div>
    <span class="sink-swatch-name">
      --sk-shadow-lg
    </span>
  </div>
</div>

The mark

The marker-pen swash from softkiwi.games, promoted to a component. It is the brand's one distinctive gesture, so it gets a token (--sk-mark-height) and a class rather than living in a page stylesheet.

Our games

Inline in a sentence the swash still reads cleanly at body size, because the stroke is measured in em rather than rem - the same class on a heading and on body copy holds the same proportion instead of turning into a strikethrough.

It also wraps, which an absolutely positioned bar cannot do: a marked phrase long enough to run past the end of its line gets a stroke on each fragment rather than one bar drawn across both lines and through the gap.

A full fill when the line needs more weight.

Show markup
<div class="sk-stack" data-gap="5">
  <h2>
    Our
    <span class="sk-mark">
      games
    </span>
  </h2>
  <p>
    Inline in a sentence the swash still
    <span class="sk-mark">
      reads cleanly
    </span>
    at body size, because the stroke is measured in em rather than rem - the same class on a heading and on body copy holds the same proportion instead of turning into a strikethrough.
  </p>
  <p class="sink-narrow">
    It also wraps, which an absolutely positioned bar cannot do:
    <span class="sk-mark">
      a marked phrase long enough to run past the end of its line
    </span>
    gets a stroke on each fragment rather than one bar drawn across both lines and through the gap.
  </p>
  <h2>
    A
    <span class="sk-mark" data-fill="true">
      full fill
    </span>
    when the line needs more weight.
  </h2>
</div>