Theme contract

Signature of the theme() mixin in src/theme/_create.scss, the tokens the framework consumes, and what happens when a theme omits a token.

Mixin signature

@mixin theme(
  $name,
  $variables: (),
  $combinations: (),
  $is-default: false,
  $root-when: null,
  $modifier-class: null,
);

Parameters

ParameterTypeDefaultEffect
$namestringrequiredReserved for diagnostics. No effect on emitted CSS.
$variablesmap()Map of scalar token short-name → value. The factory adds the --kc- prefix. Keys absent from the map are not emitted; consumers fall back to each component's hardcoded SCSS-time default.
$combinationsmap()Map of combination label → (bg, fg) tuple. Each tuple expands to --kc-<label>-bg and --kc-<label>-fg. Use map.merge(variables.$color-combinations, …) to override individual entries while keeping the rest.
$is-defaultboolfalseWhen true, emits the variables on :root unconditionally.
$root-whenstringnullWraps an emit block in a media query. Pass strings like 'prefers-color-scheme: dark'.
$modifier-classselectornullWhen set, emits a second block under that class: typically .theme-dark.

$is-default, $root-when, and $modifier-class are independent. A theme may set any subset. Setting none emits nothing: useful for parameterising a base map shared across multiple themes.

Tokens consumed by the framework

The factory does not validate the maps you pass — both $variables and $combinations are optional and may be partial. A token absent from the emitted CSS falls through to the component's SCSS-time fallback (var(--kc-bg, #ffffff) etc.). The framework consumes:

Scalar tokens ($variables)

  • bg, fg: page background and foreground (base/global).
  • link, link-hover: anchor colours (typography/link, element/button.is-link).
  • focus-ring: :focus-visible outline colour (Button, Link). Aim for ≥3:1 against every adjacent surface (WCAG 2.4.13 AAA).
  • border-radius: surface rounding (Card, Modal, etc.).
  • card-bg, card-fg: Card surface colours (module/card).

Components consume additional tokens that aren't mandatory: e.g. module/modal reads --kc-modal-backdrop (the bundled dark theme ships it; default and high-contrast don't).

Combinations ($combinations)

One (bg, fg) tuple per label in $color-combinations: by default default, primary, warning, danger, success. Each tuple expands to --kc-<label>-bg and --kc-<label>-fg, consumed by Button and Card variants.

Fallback semantics

A token omitted from the emitted block resolves to the SCSS-compile-time fallback baked into each component plugin. Those fallbacks reflect the default palette, not the theme that omitted the token. A partial dark theme that drops bg therefore renders against a white page, not the theme's intended dark surface — usually not what you want.

There is no compile-time validation: a typo (bg-color: #000 instead of bg: #000) produces a silently broken theme rather than an error. Themes are expected to ship a complete map for the surfaces they care about.

Minimal theme

// src/theme/my-theme.scss
@use '../variables';
@use 'create';

$variables: (
  bg:            #fff,
  fg:            #27282b,
  link:          #1a6347,
  link-hover:    #525a22,
  border-radius: variables.$global-border-radius,
  card-bg:       #fff,
  card-fg:       #27282b,
  focus-ring:    #1a6347,
);

$combinations: (
  default: (#fff,    #27282b),
  primary: (#2a8d69, #fff),
  warning: (#7a4f10, #fff),
  danger:  (#8c351a, #fff),
  success: (#525a22, #fff),
);

@include create.theme(
  $name: 'my-theme',
  $variables: $variables,
  $combinations: $combinations,
  $is-default: true,
);

Output shape

For each enabled emit block, the factory writes the scalar variables followed by two custom properties per combination:

/* When $is-default is true */
:root {
  --kc-bg: #fff;
  --kc-fg: #27282b;
  /* … remaining scalars … */
  --kc-default-bg: #fff;
  --kc-default-fg: #27282b;
  --kc-primary-bg: #2a8d69;
  --kc-primary-fg: #fff;
  /* … one bg/fg pair per combination … */
}

See also: --kc-* tokens, how-to: author a custom theme.