Kesko Design System’s color palette is a selection of colors that work together to create consistency in digital products.

The palette #

Neutral

var(--k-color-neutral-100) 1:1
var(--k-color-neutral-200) 1.14:1
var(--k-color-neutral-300) 1.36:1
var(--k-color-neutral-400) 1.58:1
var(--k-color-neutral-500) 1.82:1
var(--k-color-neutral-600) 2.49:1
var(--k-color-neutral-700) 3.54:1
var(--k-color-neutral-800) 4.61:1
var(--k-color-neutral-900) 7.34:1
var(--k-color-neutral-1000) 10.7:1
var(--k-color-neutral-1100) 14.74:1
var(--k-color-neutral-1200) 21:1

Beige

var(--k-color-beige-100) 1.06:1
var(--k-color-beige-200) 1.12:1
var(--k-color-beige-300) 1.25:1
var(--k-color-beige-400) 1.58:1
var(--k-color-beige-500) 1.96:1
var(--k-color-beige-600) 2.42:1
var(--k-color-beige-700) 3.01:1
var(--k-color-beige-800) 4.73:1
var(--k-color-beige-900) 6.56:1
var(--k-color-beige-1000) 8:1
var(--k-color-beige-1100) 15.18:1
var(--k-color-beige-1200) 18:1

Red

var(--k-color-red-100) 1.06:1
var(--k-color-red-200) 1.12:1
var(--k-color-red-300) 1.2:1
var(--k-color-red-400) 1.41:1
var(--k-color-red-500) 1.96:1
var(--k-color-red-600) 2.42:1
var(--k-color-red-700) 3.01:1
var(--k-color-red-800) 4.73:1
var(--k-color-red-900) 6.56:1
var(--k-color-red-1000) 8:1
var(--k-color-red-1100) 15.18:1
var(--k-color-red-1200) 18:1

Orange

var(--k-color-orange-100) 1.06:1
var(--k-color-orange-200) 1.12:1
var(--k-color-orange-300) 1.25:1
var(--k-color-orange-400) 1.58:1
var(--k-color-orange-500) 1.96:1
var(--k-color-orange-600) 2.42:1
var(--k-color-orange-700) 3.01:1
var(--k-color-orange-800) 4.73:1
var(--k-color-orange-900) 6.56:1
var(--k-color-orange-1000) 8:1
var(--k-color-orange-1100) 15.18:1
var(--k-color-orange-1200) 18:1

Yellow

var(--k-color-yellow-100) 1.05:1
var(--k-color-yellow-200) 1.09:1
var(--k-color-yellow-300) 1.2:1
var(--k-color-yellow-400) 1.3:1
var(--k-color-yellow-500) 1.43:1
var(--k-color-yellow-600) 1.64:1
var(--k-color-yellow-700) 2.3:1
var(--k-color-yellow-800) 4.2:1
var(--k-color-yellow-900) 5.5:1
var(--k-color-yellow-1000) 8:1
var(--k-color-yellow-1100) 15.18:1
var(--k-color-yellow-1200) 18:1

Green

var(--k-color-green-100) 1.06:1
var(--k-color-green-200) 1.12:1
var(--k-color-green-300) 1.25:1
var(--k-color-green-400) 1.58:1
var(--k-color-green-500) 1.96:1
var(--k-color-green-600) 2.42:1
var(--k-color-green-700) 3.01:1
var(--k-color-green-800) 4.73:1
var(--k-color-green-900) 6.56:1
var(--k-color-green-1000) 8:1
var(--k-color-green-1100) 15.18:1
var(--k-color-green-1200) 18:1

Teal

var(--k-color-teal-100) 1.06:1
var(--k-color-teal-200) 1.12:1
var(--k-color-teal-300) 1.25:1
var(--k-color-teal-400) 1.58:1
var(--k-color-teal-500) 1.96:1
var(--k-color-teal-600) 2.42:1
var(--k-color-teal-700) 3.01:1
var(--k-color-teal-800) 4.73:1
var(--k-color-teal-900) 6.56:1
var(--k-color-teal-1000) 8:1
var(--k-color-teal-1100) 15.18:1
var(--k-color-teal-1200) 18:1

Blue

var(--k-color-blue-100) 1.06:1
var(--k-color-blue-200) 1.12:1
var(--k-color-blue-300) 1.25:1
var(--k-color-blue-400) 1.58:1
var(--k-color-blue-500) 1.96:1
var(--k-color-blue-600) 2.42:1
var(--k-color-blue-700) 3.01:1
var(--k-color-blue-800) 4.73:1
var(--k-color-blue-900) 6.56:1
var(--k-color-blue-1000) 8:1
var(--k-color-blue-1100) 15.18:1
var(--k-color-blue-1200) 18:1

Purple

var(--k-color-purple-100) 1.06:1
var(--k-color-purple-200) 1.12:1
var(--k-color-purple-300) 1.25:1
var(--k-color-purple-400) 1.58:1
var(--k-color-purple-500) 1.96:1
var(--k-color-purple-600) 2.42:1
var(--k-color-purple-700) 3.01:1
var(--k-color-purple-800) 4.73:1
var(--k-color-purple-900) 6.56:1
var(--k-color-purple-1000) 8:1
var(--k-color-purple-1100) 15.18:1
var(--k-color-purple-1200) 18:1

Dark mode Experimental #

Our color system includes a dedicated neutral-dark ramp alongside the standard light neutral ramp. Together they provide the surface and text colors needed to support both light and dark modes without manual color picking. Each step in the dark neutral ramp is the semantic counterpart of the same step in the light neutral ramp:

Light mode
Dark mode
var(--k-color-neutral-100) 1:1
var(--k-color-neutral-dark-100) 1:1
var(--k-color-neutral-200) 1.14:1
var(--k-color-neutral-dark-200) 1.14:1
var(--k-color-neutral-300) 1.36:1
var(--k-color-neutral-dark-300) 1.36:1
var(--k-color-neutral-400) 1.58:1
var(--k-color-neutral-dark-400) 1.58:1
var(--k-color-neutral-500) 1.82:1
var(--k-color-neutral-dark-500) 1.82:1
var(--k-color-neutral-600) 2.49:1
var(--k-color-neutral-dark-600) 2.49:1
var(--k-color-neutral-700) 3.54:1
var(--k-color-neutral-dark-700) 3.54:1
var(--k-color-neutral-800) 4.61:1
var(--k-color-neutral-dark-800) 4.61:1
var(--k-color-neutral-900) 7.34:1
var(--k-color-neutral-dark-900) 7.34:1
var(--k-color-neutral-1000) 10.7:1
var(--k-color-neutral-dark-1000) 10.7:1
var(--k-color-neutral-1100) 14.74:1
var(--k-color-neutral-dark-1100) 14.74:1
var(--k-color-neutral-1200) 21:1
var(--k-color-neutral-dark-1200) 21:1

Choosing colors for dark mode #

Saturated colors can be converted from a light theme to a dark theme using symmetry. The 12-step palette can be divided in half, and each half becomes a mirror of the other:

  • If an element uses step 800 in light mode, it uses step 500 in dark mode.
  • If a surface uses step 200 in light mode, it uses step 1100 in dark mode.
  • The midpoint sits between steps 600 and 700.
100
200
300
400
500
600
700
800
900
1000
1100
1200
100 ↔ 1200 · 200 ↔ 1100 · 300 ↔ 1000 · 400 ↔ 900 · 500 ↔ 800 · 600 ↔ 700

Please note: While this approach provides a great starting point, it does not work for every single use case. An example could be e.g. specific brand colors that you want to keep uniform across different theme modes.

Installation #

To install Kesko’s color utilities as a dependency in your project, run:

npm install @kesko/color

Usage #

Import from @kesko/color directly only when you need to create a new color palette family or compose your own theme. The package exposes two helper functions, keskoPalette() and keskoTheme(), for exactly this.

keskoPalette() #

Creates a new Kesko color palette family with built-in defaults. The name is automatically prefixed with --k-color-, and the 12 default contrast ratios match the rest of the Kesko palette so a custom family slots in seamlessly alongside the built-in ones:

import { keskoPalette } from "@kesko/color";

const palette = keskoPalette({
  name: "palette",
  colorKeys: ["#f86800"],
});

Pass two or more colorKeys to interpolate between multiple hues across the scale:

const palette = keskoPalette({
  name: "palette",
  colorKeys: ["#f86800", "#cb4b00"],
});

The full list of provided options includes:

const palette = keskoPalette({
  /**
   * Color palette name
   * @type {string}
   */
  name: "palette",

  /**
   * Source color(s) as hex values
   * @type {string[]}
   */
  colorKeys: ["#f86800"],

  /**
   * WCAG contrast ratios for each generated color
   * @type {number[]}
   */
  ratios: [1.06, 1.12, 1.22, 1.32, 1.45, 1.7, 2.5, 4.6, 6, 9, 16, 18],

  /**
   * Interpolation colorspace
   * @type {"RGB" | "HSL" | "HSV" | "HSLuv" | "LAB" | "LCH" | "OKLAB" | "OKLCH" | "CAM02" | "CAM02p"}
   */
  colorspace: "RGB",

  /**
   * Smooth interpolation between hues
   * @type {boolean}
   */
  smooth: true,

  /**
   * Global saturation adjustment (0–100)
   * @type {number}
   */
  saturation: 100,
});

keskoTheme() #

Combines one or more color palette families against a background and resolves them into a full theme. This is the step that actually generates the 12 colors per family. Until a palette is part of a theme, only its source keys and target ratios are defined.

import { keskoTheme, background, neutral, orange } from "@kesko/color";

const theme = keskoTheme({
  colors: [neutral, orange],
  backgroundColor: background,
});

The full list of provided options includes:

const theme = keskoTheme({
  /**
   * Array of color palettes to include in the theme
   * @type {Color[]}
   */
  colors: [orange, blue],

  /**
   * Reference background for contrast calculations
   * @type {BackgroundColor}
   */
  backgroundColor: background,

  /**
   * Background lightness (0–100)
   * @type {number}
   */
  lightness: 100,

  /**
   * Global contrast multiplier
   * @type {number}
   */
  contrast: 1,

  /**
   * Global saturation adjustment (0–100)
   * @type {number}
   */
  saturation: 100,

  /**
   * Output color format
   * @type {"HEX" | "RGB" | "HSL" | "HSV" | "LAB" | "LCH" | "CAM02" | "CAM02p"}
   */
  output: "HEX",

  /**
   * Contrast formula
   * @type {"wcag2" | "wcag3"}
   */
  formula: "wcag2",
});

Included utilities #

Alongside the two helpers, @kesko/color ships the default Kesko color palette families and fully composed themes as named exports, so you can drop them straight into a custom keskoTheme() call:

  • keskoPalette(): Helper to create a new color palette family
  • keskoTheme(): Helper to compose one or more palettes into a theme
  • theme: Fully composed light theme combining all 9 families against a white background
  • darkTheme: Dark theme with the neutral-dark palette against a dark background
  • background: White (#fff) reference background for light-mode contrast math
  • darkBackground: Dark reference background for dark-mode contrast math
  • neutral: Neutral palette, pinned to white/black at extremes
  • neutralDark: Neutral-dark palette, pinned to near-black/white at extremes (inverse of neutral)
  • orange: Orange palette
  • beige: Beige palette
  • yellow: Yellow palette
  • purple: Purple palette
  • green: Green palette
  • teal: Teal palette
  • blue: Blue palette
  • red: Red palette

Accessibility #

Every step in the light palette is generated by targeting a specific WCAG 2 contrast ratio against #fff. The neutral-dark palette targets the same ratios against its dark background. As a rule of thumb:

Light mode (neutral and saturated palettes):

  • Steps 800 and above meet WCAG 2 AA (4.5:1) for normal-size text on white.
  • Step 700 meets WCAG 2 AA (3:1) for large text and non-text elements (UI components, graphical objects).
  • Steps 100–600 are intended for backgrounds, surfaces, and decorative use, not for text on white.

Dark mode (neutral-dark palette):

  • Steps 800 and above meet WCAG 2 AA (4.5:1) for normal-size text on the dark background.
  • The same ratio thresholds apply meaning that the palette is generated with identical contrast targets, just inverted.

When pairing colors against backgrounds other than the palette's reference background, always re-verify contrast with a tool such as the WebAIM contrast checker.