08
Color System

Color System In Depth

The 7 color families, 6 color roles, structural vs family colors, and how Type × Color remain completely independent.

8.1 The 7 Color Families

Every component color in CocoKits belongs to one of 7 families. Each family represents a distinct user intent:

FamilyPurposeExample Usage
neutralDefault, unaccented UIDividers, borders, muted backgrounds
brandPrimary brand identityPrimary buttons, active states, key CTAs
infoInformational / neutral-positiveInfo banners, help icons, links
successPositive confirmationSuccess toasts, completed states
warningCaution, needs attentionWarning alerts, approaching limits
dangerDestructive, error, criticalDelete buttons, error messages, validation
contrastMaximum visual weightHigh-contrast text, critical emphasis

Each family has all 6 roles. That means: 7 families × 6 roles = 42 semantic color tokens (plus structural globals).

8.2 The 6 Color Roles

Inspired by Material Design 3's color role system, adapted for CocoKits:

color
Primary Accent Swatch

The "loud" color. Bold, saturated. Used for filled backgrounds or key strokes. This is what most people think of as "the brand color."

on-color
Content ON color

Usually white or very dark. Must have high contrast against color. Used for text and icons placed on color backgrounds.

container
Soft / Tinted Background

The "quiet" version. Low saturation, tinted. Used for subtle backgrounds (light buttons, chips, soft highlights).

on-container
Content ON container

Usually dark in light mode, light in dark mode. Must read well against container. Used for text on soft backgrounds.

outline
Borders & Strokes

Typically the same hue as color but used exclusively for stroke/border contexts. Outline buttons, form field borders when active.

focus
Focus Rings

An alpha/transparent version of the family color. Used for box-shadow focus indicators to show keyboard navigation state.

8.3 How Color × Type Stay Independent

This is the most important concept for developers. Color and Type are orthogonal — they never need to know about each other.

CSS — Color class (Step 1)
/* Step 1: The Color class sets CSS variables */
.cck-button__color--brand {
  --cck-color:        var(--brand-color);
  --cck-on-color:     var(--brand-on-color);
  --cck-container:    var(--brand-container);
  --cck-on-container: var(--brand-on-container);
  --cck-outline:      var(--brand-outline);
  --cck-focus:        var(--brand-focus);
}

.cck-button__color--danger {
  --cck-color:        var(--danger-color);
  --cck-on-color:     var(--danger-on-color);
  /* ...same pattern for all 7 families... */
}
CSS — Type class (Step 2)
/* Step 2: The Type class reads whatever color was set */
.cck-button__type--primary:not(:disabled) {
  background-color: var(--cck-color);    /* reads color class's variable */
  color:            var(--cck-on-color);
  border-radius:    var(--button-radius);
}

.cck-button__type--outline {
  background-color: transparent;
  color:            var(--cck-on-container);
  border:           1px solid var(--cck-outline);
}
🔑
The Key Insight
Adding a new color family requires only one new color class. All existing types automatically work with it.
Adding a new type requires only one new type class. All existing colors automatically work with it.
The two dimensions never need to know about each other.

8.4 Structural vs Color-Family Colors

Every component uses a mix of both. The rule of thumb:

📏
Rule If the color changes when you switch color="brand" to color="danger" on the component, it's a color-family color (lives in Color/Active). If it stays the same, it's a structural color (lives in Structure collection).
ExampleTypeExplanation
Toggle thumb (always white)StructuralNever changes with color family
Toggle track when unchecked (always gray)StructuralFixed regardless of brand/danger
Toggle track when checkedColor-familyPurple for brand, red for danger
Button disabled bgStructuralAlways the same muted gray
Button primary backgroundColor-familyChanges with color prop
Focus ringsColor-familyBrand focus ≠ danger focus
Form field default borderStructuralAlways neutral, uses semantic global
Checkbox background when checkedColor-familyChanges per family

8.5 Figma Mode Inheritance

Variable modes in Figma cascade from parent frames to children. The nearest ancestor with an explicit mode set always wins.

Figma mode inheritance example
Wrapper frame — Color/Active = "brand"    ← all children default to brand
  ├── Button instance (no override)         ← inherits brand
  ├── Toggle (Color/Active = "danger")      ← overrides to danger
  │    └── Track (inherits danger)
  └── Checkbox (no override)               ← inherits brand from wrapper
⚠️
Always set modes at the instance level when you want per-component control. Setting a mode on a shared parent frame means all children inside it inherit that mode unless explicitly overridden. To debug unexpected colors, walk up the layer tree and check which ancestor has the mode set.