Kesko Themes ships per-brand CSS overrides for our design tokens. Each theme swaps the themeable subset of design tokens while leaving the rest of the token set unchanged.

Installation #

To install Kesko Themes as a dependency in your project, run:

npm install @kesko/themes

Usage #

A theme file is an override stylesheet. Import the CSS Framework first, then apply a brand theme on top:

@import "@kesko/css/dist/kesko.min.css";
@import "@kesko/themes/dist/kespro.css";

Or from HTML:

<link rel="stylesheet" href="@kesko/css/dist/kesko.min.css" />
<link rel="stylesheet" href="@kesko/themes/dist/kespro.css" />

Dark mode #

For dark mode, load the dark variant instead of the light one:

@import "@kesko/css/dist/kesko.min.css";
@import "@kesko/themes/dist/kespro-dark.css";

Each file also sets color-scheme: light or color-scheme: dark, so native browser UI such as scrollbars and form controls adapts automatically.

Switching between modes #

When both modes need to be available at runtime, scope each file to prefers-color-scheme or to an attribute you control yourself:

<link rel="stylesheet" href="/css/kespro.css" media="(prefers-color-scheme: light)" />
<link rel="stylesheet" href="/css/kespro-dark.css" media="(prefers-color-scheme: dark)" />

For explicit user control, toggle a data-theme-mode attribute on the root element and load the matching file in CSS:

@import "@kesko/themes/dist/kespro.css" (prefers-color-scheme: light);
@import "@kesko/themes/dist/kespro-dark.css" (prefers-color-scheme: dark);

Themeable tokens #

By default, each brand overrides a subset of @kesko/tokens listed below. All other tokens such as motion, shadows, layers, opacity, and the full primitive palette inherit from @kesko/tokens unchanged.

CategoryTokens
Accent colors--k-color-accent, --k-color-accent-secondary
Text on surface--k-color-text-on-accent, --k-color-text-on-secondary, --k-color-text-on-disabled, --k-color-text-on-decorative
Inline text--k-color-text-link
Borders--k-color-border-divider
Fills--k-color-fill-decorative, --k-color-fill-secondary
Backgrounds--k-color-bg-highlight, --k-color-bg-disabled, --k-color-bg-hover, --k-color-bg-inverted, --k-color-bg-subtle, --k-color-bg-muted
Radius--k-radius-button, --k-radius-input, --k-radius-checkbox, --k-radius-element
Spacing--k-space-button

Output formats #

{brand}.css #

Light mode overrides. Contains only the themeable tokens as resolved values under :root.

:root {
  color-scheme: light;
  --k-color-accent: #cb4700;
  --k-color-accent-secondary: #552c87;
  --k-radius-button: 0.25rem;
  /* ... */
}

{brand}-dark.css #

Complete dark mode. Contains all semantic color tokens (backgrounds, text, borders, fills, status, overlay) from the base dark theme, plus brand-specific dark overrides.

:root {
  color-scheme: dark;
  --k-color-bg: #1b1b1b;
  --k-color-text: #ededed;
  --k-color-accent: #f86800;
  /* ... */
}

{brand}.json #

JSON describing both light and dark resolved values, including the original alias reference and an auto flag that indicates whether each dark value was auto-generated or manually overridden:

{
  "meta": { "name": "kespro" },
  "light": {
    "--k-color-accent": { "value": "#cb4700", "ref": "{color.orange.800}" }
  },
  "dark": {
    "--k-color-accent": { "value": "#f86800", "ref": "{color.orange.700}", "auto": true }
  }
}

Dark mode generation #

Dark mode is produced from two layers, in order:

  1. Base dark theme: @kesko/tokens ships a dark mode config that maps every semantic color token to its dark equivalent. It uses the dedicated neutral-dark palette for neutrals and the palette symmetry rule for saturated colors.
  2. Brand dark overrides: each brand's theme may include a dark: section for tokens where the auto-mirrored value needs adjustment.
# Brand theme

# Light mode overrides
color:
  accent:
    base:
      value: "{color.orange.800}"

# Optional: manual dark mode overrides
dark:
  color:
    accent:
      base:
        value: "{color.orange.700}"

When a brand token has no explicit dark: entry, the symmetry rule produces the mirrored value automatically and the JSON output marks it with "auto": true.