Combinations and light variants
Combinations are the framework's primary colour contract. Every
theme-aware component — badge, box, button, alert, card, toast,
dropdown, modal, navbar, progress — paints itself by reaching into the
same shared map of named (background, foreground) tuples.
The modifier classes (.is-primary, .is-warning,
.is-danger, .is-success) are the
consumer-facing surface of that contract;
$default-combination, $enable-light, and the
runtime --kc-<combo>-* tokens are the plumbing
behind them.
This page exists so the same explanation does not have to repeat across
every component reference. Each component's ## Variables table carries
a footnote pointing back here for the cross-cutting knobs.
$color-combinations — the global map
The shared roster of combinations lives in src/variables/_color.scss
as $combinations. Each entry is a (bg, fg) tuple drawn from the
shipped palette:
$combinations: (
default: (map.get($palette, white), map.get($palette, black)),
primary: (map.get($palette, emerald), map.get($palette, white)),
warning: (map.get($palette, amber), map.get($palette, white)),
danger: (map.get($palette, coral), map.get($palette, white)),
success: (map.get($palette, moss), map.get($palette, white)),
) !default;
The first key (default) is the framework-wide "at-rest" tuple and
feeds every component's $default-combination. The remaining keys are
consumed by combinations-apply() and emit per-modifier rules
(.button.is-primary, .card.is-warning, …) plus per-theme --kc-*-bg
/ --kc-*-fg tokens via theme/_create.scss. Themes can override
individual entries (the dark theme ships darker tuples) — see the
per-theme contract below.
$default-combination — the at-rest surface paint
Every theme-aware component declares its own $default-combination
with the same shape:
$default-combination: variables.$color-combination-default !default;
It is the (bg, fg) tuple applied to a bare .button, .card, or
.toast — anything that lacks a .is-<combo> modifier. Three places to
re-tune it:
- Per-component override at
@use ... with (...)time when only one component should diverge. - Globally by retuning
variables.$color-combinationsonce and letting every component's default follow. - Per-theme by shipping a
$combinationsoverride; the runtime tokens flip on theme switch, no recompile needed.
Components never reach back into the map directly to read the default
tuple; they always go through $default-combination. That indirection
is what lets consumers shadow the default without touching the global
map.
$enable-light — the soft-tint companion flag
Every theme-aware component also carries:
$enable-light: variables.$feature-light-variants !default;
When the framework-wide flag is on (the default), each component emits
.is-<combo>.is-light selectors that paint the surface using the
soft-tint tuples — --kc-<combo>-light-bg / --kc-<combo>-light-fg
emitted by theme/_create.scss. The light variant preserves the
semantic hue while lowering the contrast intensity, useful for
body-copy-dense surfaces where the full-strength brand colour would
fight the prose (think soft warning callouts, inline danger summaries,
desaturated info toasts).
Strip the variants per-component by overriding $enable-light: false
at @use time, or globally by setting
variables.$feature-light-variants: false to drop the rules from every
component at once.
Per-theme contract
theme/_create.scss validates the colour contract for every theme.
Each theme ships a $combinations map; missing keys are filled from
the framework defaults and @warn is logged so the consumer is told
which tuples they would inherit. The dark theme demonstrates the
override pattern — it ships darker (bg, fg) pairs so combinations
read correctly against a near-black page surface — but a partial map is
the supported, lower-effort path for consumer themes.
The light-variant tokens (--kc-<combo>-light-bg /
--kc-<combo>-light-fg) are derived per theme so the soft tint always
tracks the active page surface rather than baking a single mix at
SCSS-compile time.
Why these are not in per-component variable tables
$enable-light, $default-combination, and $controls (the
reveal-mechanism flag shared by modal, navbar, dropdown, and
future stateful components) are intentionally absent from each
component's ## Variables table. Three reasons:
- Identical default across every component. The row would say
variables.$color-combination-defaultnext to every component, with identical override semantics. Per-component rows would multiply the same row ~15 times. - They derive from cross-cutting framework primitives. A consumer
rebinding
variables.$color-combinationsonce retunes every component automatically. The override surface is the global map, not the per-component knob. - Documenting them per-component is structurally misleading. It
suggests the per-component value carries product-specific semantics
("the button's notion of
default"), when it is really just the component's pointer into the shared roster.
The † see Combinations and light variants footnote under each
component's variable table is the load-bearing UX: it routes consumers
to the canonical surface (this page) when they need to re-tune the
contract.
Further reading
- Variable layers — the declarative / semantic / runtime split that combinations live inside.
- Architecture overview — the
SCSS module graph that keeps
_variables.scssfiles leaf-only. --kc-*token reference — the complete roster of runtime tokens, including the per-combination--kc-<combo>-bg/--kc-<combo>-fgand--kc-<combo>-light-bg/--kc-<combo>-light-fgpairs.