Consumer Semantic Theme tokens

The content of this file is used for supplementary documentation and generation of consumer-theme.css.

Color Themes
Theme names
Emotional tokens
Branded Palette Colors

The branded palette establishes base colors with brightness variations, ensuring accessibility and adherence to ADA guidelines. Each color family includes up to 4 variants: xl (extra light), l (light), d (dark), xd (extra dark).

Brightness Levels
xl (extra light)
Lightest variant, typically used in light themes for backgrounds and surfaces. High brightness, low saturation.
Text color pairing: Uses xd (extra dark) for maximum contrast.
Example: xl background with xd text
l (light)
Light variant, primary color for light themes. Base emotional color in light mode.
Text color pairing: Uses d (dark) or xd (extra dark) for contrast.
Example: l background with d text
d (dark)
Dark variant, primary color for dark themes. Base emotional color in dark mode.
Text color pairing: Uses l (light) or xl (extra light) for contrast.
Example: d background with l text
xd (extra dark)
Darkest variant, typically used in dark themes for backgrounds and surfaces. Low brightness, higher saturation.
Text color pairing: Uses xl (extra light) for maximum contrast.
Example: xd background with xl text

Contrast Principle: Brightness levels are paired with their opposite extremes to ensure WCAG-compliant contrast ratios:

color ranges: l is used as base color for light mode; d as its opposite variation. The brightness variations in between are generated by css formula.

darl/light: l is used as base color for light mode; d in dark. See the css light-dark use bellow as a switch.

text colors: uses primary page text (comfort) with xd↔xl in light/dark mode. Only in special cases when contrast is not sufficient it is overridden (danger).

        --cem-palette-comfort-text    : light-dark(var( --cem-color-cyan-xd ), var( --cem-color-cyan-xl ));
        --cem-palette-comfort-text-x  : light-dark(var( --cem-color-cyan-xl ), var( --cem-color-cyan-xd ));

        --cem-palette-danger          : light-dark(var( --cem-color-red-l             ), var( --cem-color-red-d           ));
        --cem-palette-danger-text     : light-dark(var( --cem-palette-comfort-text-x  ), var( --cem-palette-comfort-text  ));
        --cem-palette-danger-x        : light-dark(var( --cem-color-red-d             ), var( --cem-color-red-l           ));
        --cem-palette-danger-text-x   : light-dark(var( --cem-color-orange-xl         ), var( --cem-color-orange-xl       ));

        --cem-action-destructive-hover-background: color-mix(in srgb, var(--cem-palette-danger ) 60%, var(--cem-palette-danger-x ));
        --cem-action-destructive-hover-text: var(--cem-palette-danger-text );
Usage in Color System

These branded colors form the foundation of the theme system:

  1. Emotional Palette: Branded colors map to emotional tokens via light-dark() function
  2. State Variations: Colors are mixed using color-mix() to create state progressions
  3. Accessibility: All color pairs maintain WCAG contrast ratios (≥4.5:1 for text, ≥3:1 for UI elements)
  4. Material Design Compatibility: Tokens align with Angular Material and Material Design 3 color systems
Cyan Family
Blue Family
Red Family
Purple Family
Orange/Yellow Family
Brown Family
Grey Family

extreme values for grey are white and black

Note: Grey family has only 2 variants (l, d) as neutral colors require fewer gradations.

Emotional Palette

Emotional palette tokens create the bridge between branded colors and action states. The emotional palette is defined by marketing strategy and expressed through branded colors using design rationale. Should your app aims to be aggressively seductive, calmy relaxing, or office focused will affect the choice of emotional palette.

Emotional palette tokens form the link between branded colors and action states, so users consistently feel what the interface intends to communicate.

Emotional to Action Intent Mapping

Each action intent maps to a specific emotional palette:

Action Intent Emotional Palette Base Color Purpose
explicit creativity Purple User explicitly/intentionally clicks or activates
primary trust Blue Default, confirmatory, triggered by ENTER
contextual comfort Cyan/White Integrated within specific context (toolbar/menu)
alternate enthusiasm Orange/Yellow Triggering non-usual alternative flow
destructive danger Red Risky, potentially harmful, requires caution
Emotional Token Variants

Each emotional token comes in four base variants to ensure reliable text contrast and accessibility. Intermediate colors are generated between these variants to represent nuances such as action state importance.

color per light theme
Each of four emotion variations bellow have own values in each of light/dark/contrast/native theme.
For light and dark themes the light-dark() CSS function switches the same variation to theme specific one.
--cem-palette-{emotion}
Base emotional color. Used as background/base surface/ primary filler color.
--cem-palette-{emotion}-x
Extreme variant (inverted) of the base emotional color. Used by color-mix() formulas for state progression in Emotional palette gradations
--cem-palette-{emotion}-text
Ink/Text color for use on the base emotional background. Ensures WCAG-compliant contrast (≥4.5:1 for text).
--cem-palette-{emotion}-text-x
Ink/Text color for use on the extreme variant background. The color is triggered from --cem-palette-{emotion}-text when background become close to --cem-palette-{emotion}-x for contrast maintaining and to emphasize the significance of deviation. As in `active/pending` states of `actions`

Usage Pattern:

/* Base tokens switch between light/dark mode */
--cem-palette-comfort: light-dark(var(--cem-color-cyan-xl), var(--cem-color-cyan-xd));
--cem-palette-comfort-text: light-dark(var(--cem-color-cyan-xd), var(--cem-color-cyan-xl));

/* State variations use color-mix() to create progression */
--cem-action-hover-background: color-mix(in srgb, var(--cem-palette-comfort) 60%, var(--cem-palette-comfort-x));
        
Comfort Palette

Purpose: Light scene color for reading backgrounds. Represents purity, innocence, cleanliness, peace.

System Mapping: Canvas (system color)

Warmth: Cool

CSS Definition:

--cem-palette-comfort       : light-dark(var(--cem-color-cyan-xl), var(--cem-color-cyan-xd));
--cem-palette-comfort-text  : light-dark(var(--cem-color-cyan-xd), var(--cem-color-cyan-xl));
--cem-palette-comfort-x     : light-dark(var(--cem-color-cyan-xd), var(--cem-color-cyan-xl));
--cem-palette-comfort-text-x: light-dark(var(--cem-color-cyan-xl), var(--cem-color-cyan-xd));
        
Calm Palette

Purpose: Dark scene color for minimal object environments. Represents mystery, elegance, power.

System Mapping: CanvasText (system color)

Warmth: Cool

CSS Definition:

--cem-palette-calm      : light-dark(var(--cem-color-cyan-l), var(--cem-color-cyan-d));
--cem-palette-calm-text : var(--cem-palette-comfort-text);
--cem-palette-calm-x    : light-dark(var(--cem-color-cyan-d), var(--cem-color-cyan-l));
--cem-palette-calm-text-x: var(--cem-palette-comfort-text-x);
        
Trust Palette

Purpose: Primary action backgrounds, attention-grabbing elements. Represents reliability, confidence.

System Mapping: Highlight with HighlightText (system colors)

Warmth: Cool

CSS Definition:

--cem-palette-trust      : light-dark(var(--cem-color-blue-l), var(--cem-color-blue-d));
--cem-palette-trust-text : light-dark(var(--cem-palette-comfort-text), white);
--cem-palette-trust-x    : light-dark(var(--cem-color-blue-d), var(--cem-color-blue-l));
--cem-palette-trust-text-x: var(--cem-palette-comfort-text-x);
        
Danger Palette

Purpose: Alert messages, destructive actions, error states. Represents urgency, caution, passion.

System Mapping: N/A (uses red)

Warmth: Warm

CSS Definition:

--cem-palette-danger      : light-dark(var(--cem-color-red-l), var(--cem-color-red-d));
--cem-palette-danger-text : light-dark(var(--cem-palette-comfort-text-x), var(--cem-palette-comfort-text));
--cem-palette-danger-x    : light-dark(var(--cem-color-red-d), var(--cem-color-red-l));
--cem-palette-danger-text-x: light-dark(var(--cem-color-orange-xl), var(--cem-color-orange-xl));
        
Creativity Palette

Purpose: Brand identity (brand-1), creative elements, explicit actions. Represents luxury, innovation, royalty.

System Mapping: ButtonFace with ButtonText (system colors)

Warmth: Cool

CSS Definition:

--cem-palette-creativity      : light-dark(var(--cem-color-purple-l), var(--cem-color-purple-d));
--cem-palette-creativity-text : var(--cem-palette-comfort-text);
--cem-palette-creativity-x    : light-dark(var(--cem-color-purple-d), var(--cem-color-purple-l));
--cem-palette-creativity-text-x: var(--cem-palette-comfort-text-x);
        
Enthusiasm Palette

Purpose: Brand identity (brand-2), alternate flow actions, positive feedback. Represents warmth, optimism, energy.

System Mapping: Mark with MarkText (system colors)

Warmth: Warm

CSS Definition:

--cem-palette-enthusiasm      : light-dark(var(--cem-color-orange-l), var(--cem-color-orange-d));
--cem-palette-enthusiasm-text : light-dark(var(--cem-palette-comfort-text), white);
--cem-palette-enthusiasm-x    : light-dark(var(--cem-color-orange-xd), var(--cem-color-orange-xl));
--cem-palette-enthusiasm-text-x: var(--cem-palette-comfort-text-x);
        
Conservative Palette

Purpose: Brand identity (brand-3), disabled states, neutral elements. Represents stability, earthiness, restraint.

System Mapping: GrayText (system color)

Warmth: Neutral

CSS Definition:

--cem-palette-conservative      : light-dark(var(--cem-color-grey-l), var(--cem-color-grey-d));
--cem-palette-conservative-text : var(--cem-palette-comfort-text);
--cem-palette-conservative-x    : light-dark(var(--cem-color-grey-d), var(--cem-color-grey-l));
--cem-palette-conservative-text-x: var(--cem-palette-comfort-text-x);
        
Actions
Action selectors .action
Action Intent Categories

Action intents categorize user interactions by their purpose and consequence, guiding both visual design and user expectations. Each intent maps to a specific emotional palette to create consistent psychological associations.


Explicit Intent — .explicit, [flow="explicit"], [explicit]
Purpose
Actions that require deliberate, conscious user activation. The user must explicitly choose to engage with these elements.
Emotional Mapping
Creativity (Purple/Violet) — Represents uniqueness, deliberate choice, and creative decision-making.
Use Cases
  • Custom action buttons that initiate non-standard workflows
  • Feature toggles requiring conscious activation
  • Specialized tools or operations outside the main flow
  • Actions that open new contexts or modes
Trigger Method
Mouse click, touch tap, or explicit keyboard activation (Space on focused element)
Visual Characteristics
Purple/violet background distinguishes these from standard actions, signaling intentionality
Example
Primary Intent — .primary, [flow="primary"], [primary]
Purpose
Default, confirmatory actions representing the expected next step in a workflow. These are the "happy path" actions.
Emotional Mapping
Trust (Blue) — Conveys reliability, safety, and confidence in moving forward.
Use Cases
  • Submit buttons in forms
  • Confirmation dialogs ("OK", "Continue", "Save")
  • Next step in wizards or multi-step processes
  • Primary call-to-action buttons
Trigger Method
Triggered by ENTER key when form/dialog is focused, or explicit click/tap
Visual Characteristics
Blue background signals trustworthiness and forward progress
Example
Contextual Intent — .contextual, [flow="contextual"], [contextual]
Purpose
Actions integrated within a specific UI context (toolbar, menu, inline controls) that operate on or within that context.
Emotional Mapping
Comfort (Cyan/White/Canvas) — Blends with the interface, providing calm, non-intrusive functionality.
Use Cases
  • Toolbar buttons (bold, italic, align)
  • Menu items and dropdown options
  • Inline editing controls
  • Navigation elements within a panel
  • Filter and sort controls
Trigger Method
Click, tap, or keyboard selection within context
Visual Characteristics
Subtle, blends with canvas/background. No default outline in some themes to reduce visual noise.
Example
Alternate Intent — .alternate, [flow="alternate"], [alternate]
Purpose
Actions that trigger non-standard or alternative workflows, offering secondary paths or optional features.
Emotional Mapping
Enthusiasm (Orange/Yellow) — Signals energy, optimism, and alternative possibilities without danger.
Use Cases
  • Cancel or back buttons
  • Skip or bypass options
  • Alternative save options ("Save as Draft")
  • Secondary actions in dialogs
  • Feature previews or beta options
Trigger Method
Explicit click/tap. Often ESC key for cancel operations.
Visual Characteristics
Orange/yellow background distinguishes from primary flow while remaining inviting
Example
Destructive Intent — .destructive, [flow="destructive"], [destructive]
Purpose
High-risk actions that are potentially harmful, irreversible, or require extreme caution. These actions may result in data loss or significant changes.
Emotional Mapping
Danger (Red) — Universal signal for caution, urgency, and potential negative consequences.
Use Cases
  • Delete buttons
  • Remove or uninstall actions
  • Reset or clear operations
  • Permanent data modifications
  • Account termination or logout
Trigger Method
Explicit click/tap only. Should often require confirmation.
Visual Characteristics
Red background immediately signals danger and prompts careful consideration
Best Practice
⚠️ Destructive actions should typically be accompanied by confirmation dialogs or require a secondary verification step.
Example
Intent ➢ Emotion ➢ Color Mapping

This table shows the complete chain from action intent through emotional palette to branded colors:

Action Intent Emotional Palette Branded Color (Light Mode) Branded Color (Dark Mode) CSS Token
explicit creativity purple-l (#e1bee7) purple-d (#6a1b9a) --cem-action-explicit-default-background
primary trust blue-l (#d7e3ff) blue-d (#002f65) --cem-action-primary-default-background
contextual comfort cyan-xl (#f1fefe) cyan-xd (#001010) --cem-action-contextual-default-background
alternate enthusiasm orange-l (#ffe082) orange-d (#723600) --cem-action-alternate-default-background
destructive danger red-l (#ba1a1a) red-d (#690005) --cem-action-destructive-default-background

Note: The automatic theme switching is handled by the light-dark() CSS function in the emotional palette tokens, which reference these branded colors.

CSS Selector Patterns

Each intent can be applied using multiple selector patterns for maximum compatibility:

/* Class selector */
.action.explicit { }
.action.primary { }

/* Attribute with value */
.action[flow="explicit"] { }
.action[flow="primary"] { }

/* Boolean attribute */
.action[explicit] { }
.action[primary] { }
        

The triple-selector pattern ensures compatibility across different framework conventions and coding styles. On application level it is sufficient to choose only one.

Action States Overview

Action states represent different conditions and user interactions. States can be reflected by two visual aspects:

State Progression & Color-Mix Formulas

States progress from least to most emphasized through color-mix() formulas that blend the base emotional color with its extreme variant:

Background-Driven State Progression ( discussion + diagram )
disabled    → readonly   → editable   → default → indeterminate → hover    → active  → pending
(30% mix)      (80% mix)     (90% mix)     (base)      (90% mix)         (60-70% mix)  (25% mix)   (5% mix)

├─────────────────────────────┼──────────────────────────────────────────────────────────────┤
  Toward conservative color      Toward extreme variant (-x)
  (less emphasized)              (more emphasized, inverted text)
disabled (30% mix with conservative-x)
Most desaturated state. Mixes 30% of the intent's emotional color with 70% conservative extreme color.
color-mix(in srgb, var(--cem-palette-{emotion}) 30%, var(--cem-palette-conservative-x))
Text: Uses --cem-palette-conservative-text-x (grey text)
readonly (80% mix)
Subtly indicates non-editable content. 80% base color, 20% extreme variant.
color-mix(in srgb, var(--cem-palette-{emotion}) 80%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text
editable (90% mix)
Nearly full base color with slight intensity. 90% base color, 10% extreme variant.
color-mix(in srgb, var(--cem-palette-{emotion}) 90%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text
default (base, 100%)
Pure emotional palette color without mixing. Baseline state.
var(--cem-palette-{emotion})
Text: Uses --cem-palette-{emotion}-text
indeterminate (90% mix)
Same formula as editable. Used for indeterminate checkboxes or undefined values.
color-mix(in srgb, var(--cem-palette-{emotion}) 90%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text
hover (60-70% mix)
Noticeable darkening/intensifying on hover. Mix percentage varies by intent (explicit/alternate: 70%, others: 60%).
color-mix(in srgb, var(--cem-palette-{emotion}) 60-70%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text
active (25% mix)
Pressed/clicked state. Heavily mixed toward extreme variant, triggering text color inversion.
color-mix(in srgb, var(--cem-palette-{emotion}) 25%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text-x (inverted text)
pending (5% mix)
Most extreme background state during async operations. Nearly fully inverted, with animated stripes.
color-mix(in srgb, var(--cem-palette-{emotion}) 5%, var(--cem-palette-{emotion}-x))
Text: Uses --cem-palette-{emotion}-text-x (inverted text)
Special: Animated diagonal stripes using linear-gradient and background-position animation
Complete Token Reference (Intent × State)

All action states generate CSS custom properties in the format:

Token Matrix: Explicit Intent
State Background Token Text Token Status
disabled--cem-action-explicit-disabled-background--cem-action-explicit-disabled-text✓ Defined
readonly--cem-action-explicit-readonly-background--cem-action-explicit-readonly-text✓ Defined
editable--cem-action-explicit-editable-background--cem-action-explicit-editable-text✓ Defined
required--cem-action-explicit-required-background--cem-action-explicit-required-text✓ Defined
default--cem-action-explicit-default-background--cem-action-explicit-default-text✓ Defined
indeterminate--cem-action-explicit-indeterminate-background--cem-action-explicit-indeterminate-text✓ Defined
target--cem-action-explicit-target-background--cem-action-explicit-target-text✗ Undefined (zebra only)
focus--cem-action-explicit-focus-background--cem-action-explicit-focus-text✗ Undefined (zebra only)
hover--cem-action-explicit-hover-background--cem-action-explicit-hover-text✓ Defined
selected--cem-action-explicit-selected-background--cem-action-explicit-selected-text✗ Undefined (zebra only)
active--cem-action-explicit-active-background--cem-action-explicit-active-text✓ Defined
pending--cem-action-explicit-pending-background--cem-action-explicit-pending-text✓ Defined

Note: The same token pattern applies to all intents: primary, contextual, alternate, and destructive. States marked "Undefined" rely on zebra outlines rather than background colors.

Zebra Outline System

Three states use zebra striped outlines instead of background color changes: selected, focus, and target. This approach separates selection/navigation from action state.

Zebra Color Tokens

--cem-zebra-color-0
Innermost stripe (base surface). Default: Canvas
--cem-zebra-color-1
Second stripe (focus indicator). Changes to CanvasText when focused.
--cem-zebra-color-2
Third stripe (selection indicator). Changes to SelectedItem when selected (native theme).
--cem-zebra-color-3
Outermost stripe (target indicator). Changes to SelectedItem when targeted by URL hash (native theme).

Zebra Implementation

Zebra outlines are implemented using box-shadow with multiple layers:

--cem-action-box-shadow:
    0 0 0 2px var(--cem-zebra-color-1),  /* 2px wide stripe */
    0 0 0 4px var(--cem-zebra-color-2),  /* 2px wide stripe (4px - 2px) */
    0 0 0 6px var(--cem-zebra-color-3);  /* 2px wide stripe (6px - 4px) */
        

State-Specific Zebra Behavior

Focus (:focus, :focus-visible, :focus-within)
Sets --cem-zebra-color-1 to visible contrast color (typically CanvasText).
Creates innermost colored stripe to indicate keyboard focus.
Selectors: &[focus], &:focus, &:focus-visible, &:focus-within, &.focus
Selected ([selected], .selected)
Sets --cem-zebra-color-2 to selection color (typically SelectedItem or trust palette).
Creates middle stripe to indicate item selection (checkboxes, radio buttons, menu items).
Selectors: &[selected], &.selected
Target (:target, [target])
Sets --cem-zebra-color-3 to target color (typically SelectedItem or enthusiasm palette).
Creates outermost stripe when element is URL hash target (e.g., #element-id).
Selectors: &[target], &:target, &.target

Visual Example

Contrast Theme Behavior

In high-contrast themes (contrast-light, contrast-dark):

Theme Variants

The theme system supports multiple visual themes that adapt to different user preferences, accessibility needs, and system settings. Each theme maintains consistent token structure while adjusting color values and visual emphasis techniques.

Native Theme

Purpose: The Native theme honors operating system and browser color preferences, using system-provided colors to ensure seamless integration with the user's environment. This theme automatically respects OS-level settings like dark mode, high contrast, and forced colors.

System Color Mappings

The Native theme maps emotional palette tokens to CSS system colors, allowing the interface to adapt automatically to user preferences in OS/browser settings:

👁️ Emotional Token System Color (Background) System Color (Text) Purpose
⬤ comfort Canvas CanvasText Main reading surface and body text
⬤ calm CanvasText Canvas Inverted surface (dark elements on light themes)
⬤ trust Highlight HighlightText Primary action buttons, selected text background
⬤ creativity ButtonFace ButtonText Standard button backgrounds
⬤ enthusiasm Mark MarkText Marked/highlighted content (HTML <mark>)
⬤ conservative GrayText Canvas Disabled states, secondary text
⬤ danger N/A (uses red) CanvasText Errors, destructive actions (no system equivalent)

Zebra Outline System Colors

The zebra outline system for focus, selected, and target states also uses system colors:

Focused — --cem-zebra-color-1
Focused: CanvasText (visible focus indicator)
Unfocused: Canvas (invisible, blends with background)
Selected — --cem-zebra-color-2
Selected: SelectedItem (system selection color)
Unselected: Canvas (invisible)
Target — --cem-zebra-color-3
Targeted: SelectedItem (marks URL hash target)
Not targeted: Canvas (invisible)

Fallback Behavior

When system colors are unavailable or not supported by the browser, the Native theme falls back to branded color tokens:

/* System color with fallback */
--cem-palette-comfort: Canvas;
/* If Canvas unavailable, fallback chain: */
--cem-palette-comfort: var(--cem-surface, var(--cem-color-cyan-xl));

/* Selection color fallback */
--cem-zebra-color-2: SelectedItem;
/* If SelectedItem unavailable: */
--cem-zebra-color-2: var(--cem-accent-600, var(--cem-palette-trust));
        
System Color Fallback Token Final Fallback
Canvas--cem-surface--cem-color-cyan-xl (light) / --cem-color-cyan-xd (dark)
CanvasText--cem-ink-strong--cem-color-cyan-xd (light) / --cem-color-cyan-xl (dark)
SelectedItem--cem-accent-600--cem-palette-trust
SelectedItemText--cem-ink-on-accent--cem-palette-trust-text
Highlight--cem-palette-trust--cem-color-blue-l (light) / --cem-color-blue-d (dark)
HighlightText--cem-palette-trust-textwhite / --cem-color-blue-xl
GrayText--cem-palette-conservative--cem-color-grey-l (light) / --cem-color-grey-d (dark)

Forced-Colors Mode Support

The Native theme provides full support for Windows High Contrast Mode and other forced-colors environments through the @media (forced-colors: active) media query:

@media (forced-colors: active) {
    /* All custom colors are overridden by system colors */
    /* Backgrounds become Canvas or ButtonFace */
    /* All text becomes CanvasText or ButtonText */
    /* Borders and outlines use system colors automatically */

    .action {
        /* System enforces color choices for maximum contrast */
        background-color: ButtonFace;
        color: ButtonText;
        border-color: ButtonText;
    }

    .action:hover {
        /* System provides hover colors */
        background-color: SelectedItem;
        color: SelectedItemText;
    }
}
        

Key behaviors in forced-colors mode:

Advantages of Native Theme

Example CSS Implementation

[data-theme="native"],
.cem-theme-native {
    /* Emotional palette → System colors */
    --cem-palette-comfort: Canvas;
    --cem-palette-comfort-text: CanvasText;
    --cem-palette-trust: Highlight;
    --cem-palette-trust-text: HighlightText;
    --cem-palette-creativity: ButtonFace;
    --cem-palette-creativity-text: ButtonText;

    /* Zebra colors */
    --cem-zebra-color-0: Canvas;
    --cem-zebra-color-1: Canvas; /* Changes to CanvasText on :focus */
    --cem-zebra-color-2: Canvas; /* Changes to SelectedItem on [selected] */
    --cem-zebra-color-3: Canvas; /* Changes to SelectedItem on :target */
}
        
Light and Dark Themes

Purpose: Light and Dark themes use the emotional palette with branded colors, automatically switching between light and dark variants based on the user's color-scheme preference or explicit theme selection.

Color-Scheme Declarations

The theme switching mechanism relies on the CSS color-scheme property and the light-dark() function:

/* Light theme */
[data-theme="light"],
.cem-theme-light {
    color-scheme: light;
}

/* Dark theme */
[data-theme="dark"],
.cem-theme-dark {
    color-scheme: dark;
}

/* Automatic (respects user preference) */
:root {
    color-scheme: light dark;  /* Supports both schemes */
}
        

How it works:

Automatic Theme Switching via light-dark()

All emotional palette tokens use the light-dark() CSS function to automatically select appropriate colors based on the active color scheme:

/* Comfort palette - switches from light cyan to dark cyan */
--cem-palette-comfort: light-dark(
    var(--cem-color-cyan-xl),   /* Light theme: extra light cyan (#f1fefe) */
    var(--cem-color-cyan-xd)    /* Dark theme: extra dark cyan (#001010) */
);

--cem-palette-comfort-text: light-dark(
    var(--cem-color-cyan-xd),   /* Light theme: dark text on light bg */
    var(--cem-color-cyan-xl)    /* Dark theme: light text on dark bg */
);

/* Trust palette - switches from light blue to dark blue */
--cem-palette-trust: light-dark(
    var(--cem-color-blue-l),    /* Light theme: light blue (#d7e3ff) */
    var(--cem-color-blue-d)     /* Dark theme: dark blue (#002f65) */
);

/* Danger palette - switches from light red to dark red */
--cem-palette-danger: light-dark(
    var(--cem-color-red-l),     /* Light theme: light red (#ba1a1a) */
    var(--cem-color-red-d)      /* Dark theme: dark red (#690005) */
);
        

Key principle: Text colors are inverted relative to background colors to maintain proper contrast:

Complete Token Switching Pattern

Each emotional palette has four variants that switch in coordinated pairs:

Token Light Theme Value Dark Theme Value Purpose
--cem-palette-trust blue-l (#d7e3ff) blue-d (#002f65) Base color
--cem-palette-trust-text comfort-text (dark) white Text on base
--cem-palette-trust-x blue-d (#002f65) blue-l (#d7e3ff) Extreme variant (inverted)
--cem-palette-trust-text-x comfort-text-x (light) comfort-text-x (dark) Text on extreme

State Progression in Light/Dark Themes

The color-mix() formulas work consistently across both themes because they mix the base emotional color with its extreme variant:

Light Theme Progression
/* Trust palette in light theme */
Base:   blue-l (#d7e3ff)  ──┐
                            │ mix
Extreme: blue-d (#002f65) ──┘

default: 100% blue-l
hover:    60% blue-l + 40% blue-d
         (gets darker)
active:   25% blue-l + 75% blue-d
         (very dark, inverted text)
                
Dark Theme Progression
/* Trust palette in dark theme */
Base:   blue-d (#002f65)  ──┐
                            │ mix
Extreme: blue-l (#d7e3ff) ──┘

default: 100% blue-d
hover:    60% blue-d + 40% blue-l
         (gets lighter)
active:   25% blue-d + 75% blue-l
         (very light, inverted text)
                

Result: In light themes, interactions make colors darker and more saturated. In dark themes, interactions make colors lighter and more vibrant. The direction is opposite, but the perceptual emphasis (increased contrast and intensity) is consistent.

Theme-Specific Overrides

While most tokens use light-dark() for automatic switching, specific cases may require explicit overrides:

/* Example: Different shadow styles per theme */
[data-theme="light"] {
    --cem-action-box-shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.15);
}

[data-theme="dark"] {
    --cem-action-box-shadow-hover: 0 2px 8px rgba(255, 255, 255, 0.1);
}

/* Example: Adjusting opacity for dark theme */
[data-theme="dark"] {
    --cem-surface-overlay-opacity: 0.12;  /* More visible on dark */
}

[data-theme="light"] {
    --cem-surface-overlay-opacity: 0.08;  /* Subtle on light */
}
        

Zebra Outlines in Light/Dark Themes

Zebra stripe colors also adapt to the theme using the same emotional palette tokens:

State Zebra Color Variable Light Theme Dark Theme
Focus --cem-zebra-color-1 comfort-text (dark) comfort-text (light)
Selected --cem-zebra-color-2 trust (blue-l) trust (blue-d)
Target --cem-zebra-color-3 enthusiasm (orange-l) enthusiasm (orange-d)

Browser Support and Fallbacks

The light-dark() function is supported in modern browsers (Chrome 123+, Firefox 120+, Safari 17.5+). For older browsers, provide fallbacks:

/* Fallback for browsers without light-dark() support */
:root {
    /* Default to light theme values */
    --cem-palette-comfort: var(--cem-color-cyan-xl);
    --cem-palette-comfort-text: var(--cem-color-cyan-xd);
}

/* Modern browsers with light-dark() */
@supports (color: light-dark(white, black)) {
    :root {
        --cem-palette-comfort: light-dark(
            var(--cem-color-cyan-xl),
            var(--cem-color-cyan-xd)
        );
        --cem-palette-comfort-text: light-dark(
            var(--cem-color-cyan-xd),
            var(--cem-color-cyan-xl)
        );
    }
}

/* Legacy dark theme support via media query */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --cem-palette-comfort: var(--cem-color-cyan-xd);
        --cem-palette-comfort-text: var(--cem-color-cyan-xl);
    }
}
        

Example: Switching Between Light and Dark

Current Theme

This box uses light-dark() to automatically adapt.

Advantages of Light/Dark Theme System

Contrast Themes (contrast-light, contrast-dark)

Purpose: High contrast themes are designed for users with visual impairments or those working in challenging lighting conditions. These themes use a fundamentally different approach: backgrounds are neutralized to base surface colors, and zebra stripes become the primary state indicator for all states, not just focus/selected/target.

Core Design Principle: Zebra-Only State Signaling

Unlike standard light/dark themes that use background color changes for most states, contrast themes:

Why this approach? In high contrast environments, subtle background color variations can be difficult to perceive. By neutralizing backgrounds and using bold, high-contrast outlines, all state information remains clearly visible regardless of ambient lighting or visual acuity.

Neutralized Backgrounds

All action state backgrounds collapse to the base surface color:

/* contrast-light theme */
[data-theme="contrast-light"] {
    /* All intents and states use same neutral background */
    --cem-action-explicit-default-background: var(--cem-palette-comfort);
    --cem-action-explicit-hover-background: var(--cem-palette-comfort);
    --cem-action-explicit-active-background: var(--cem-palette-comfort);

    --cem-action-primary-default-background: var(--cem-palette-comfort);
    --cem-action-primary-hover-background: var(--cem-palette-comfort);
    /* ... all other intents and states ... */

    /* Background is always comfort (light canvas) */
    background-color: var(--cem-palette-comfort);  /* #f1fefe in light */
}

/* contrast-dark theme */
[data-theme="contrast-dark"] {
    /* All intents and states use dark neutral background */
    --cem-action-explicit-default-background: var(--cem-palette-comfort);
    --cem-action-explicit-hover-background: var(--cem-palette-comfort);
    /* ... backgrounds stay consistent ... */

    /* Background is always comfort (dark canvas) */
    background-color: var(--cem-palette-comfort);  /* #001010 in dark */
}
        

Result: Instead of hover making a button darker, hover changes the zebra stripe color. Instead of active making a button very dark, active makes the zebra stripe more intense.

4-Strip Zebra Pattern

Contrast themes expand the zebra system from 3 stripes to 4 stripes, adding an additional layer for intent signaling:

Stripe Layer Variable Normal Themes (3-strip) Contrast Themes (4-strip) Width
Layer 0 (innermost) --cem-zebra-color-0 Base surface (invisible) Intent color (e.g., purple for explicit) 3px
Layer 1 --cem-zebra-color-1 Focus indicator Focus indicator (comfort-text when focused) 3px
Layer 2 --cem-zebra-color-2 Selected indicator Selected indicator (trust color when selected) 3px
Layer 3 (outermost) --cem-zebra-color-3 Target indicator Target indicator (enthusiasm color when targeted) 3px

Key difference: In normal themes, --cem-zebra-color-0 is the base surface (invisible). In contrast themes, it becomes the innermost visible stripe carrying the intent's emotional color.

Zebra Color Assignment in Contrast Themes

Each action intent gets its own base zebra color that matches its emotional palette:

/* contrast-light or contrast-dark */
[data-theme*="contrast"] {
    /* Layer 0: Intent-specific base color */
    .action.explicit {
        --cem-zebra-color-0: var(--cem-palette-creativity);  /* Purple */
    }
    .action.primary {
        --cem-zebra-color-0: var(--cem-palette-trust);       /* Blue */
    }
    .action.contextual {
        --cem-zebra-color-0: var(--cem-palette-comfort-text); /* Text color */
    }
    .action.alternate {
        --cem-zebra-color-0: var(--cem-palette-enthusiasm);  /* Orange */
    }
    .action.destructive {
        --cem-zebra-color-0: var(--cem-palette-danger);      /* Red */
    }

    /* Layers 1-3: State-dependent (focus, selected, target) */
    /* These change from transparent to visible when state activates */
}
        

State Variations via Zebra Intensity

Since backgrounds are neutral, state progressions (hover, active, pending) are shown through zebra color mixing:

/* Default state: Base intent color in zebra-0 */
.action.primary {
    --cem-zebra-color-0: var(--cem-palette-trust);  /* Pure blue */
}

/* Hover state: Mix toward extreme variant */
.action.primary:hover {
    --cem-zebra-color-0: color-mix(
        in srgb,
        var(--cem-palette-trust) 60%,
        var(--cem-palette-trust-x) 40%
    );
}

/* Active state: Heavy mix toward extreme */
.action.primary:active {
    --cem-zebra-color-0: color-mix(
        in srgb,
        var(--cem-palette-trust) 25%,
        var(--cem-palette-trust-x) 75%
    );
}

/* Disabled state: Mix toward conservative */
.action.primary:disabled {
    --cem-zebra-color-0: color-mix(
        in srgb,
        var(--cem-palette-trust) 30%,
        var(--cem-palette-conservative-x) 70%
    );
}
        

Visual effect: The innermost stripe changes intensity/hue based on state, while maintaining high contrast against the neutral background.

Increased Outline Thickness

Contrast themes use thicker stripes for improved visibility:

Theme Type Stripe Width Total Outline Width Stripe Count
Light / Dark 2px per stripe 6px (3 × 2px) 3 stripes
Contrast Light / Dark 3px per stripe 12px (4 × 3px) 4 stripes
/* Normal themes */
--cem-zebra-strip-size: 2px;
--cem-action-box-shadow:
    0 0 0 2px var(--cem-zebra-color-1),  /* 2px stripe */
    0 0 0 4px var(--cem-zebra-color-2),  /* 2px stripe */
    0 0 0 6px var(--cem-zebra-color-3);  /* 2px stripe */

/* Contrast themes */
[data-theme*="contrast"] {
    --cem-zebra-strip-size: 3px;
    --cem-action-box-shadow:
        0 0 0 3px var(--cem-zebra-color-0),   /* NEW: 3px intent stripe */
        0 0 0 6px var(--cem-zebra-color-1),   /* 3px focus stripe */
        0 0 0 9px var(--cem-zebra-color-2),   /* 3px selected stripe */
        0 0 0 12px var(--cem-zebra-color-3);  /* 3px target stripe */
}
        

Contrast Requirements

High contrast themes enforce stricter contrast ratios to ensure visibility:

Visual Comparison: Normal vs Contrast Themes

Normal Theme (Light)
  • ✓ Background colors vary by state
  • ✓ 3-strip zebra for focus/selected/target only
  • ✓ 2px stripe width
  • ✓ Subtle color progressions
Primary button (background-driven)
Contrast Theme (contrast-light)
  • ✓ All backgrounds neutral (same color)
  • ✓ 4-strip zebra for ALL states
  • ✓ 3px stripe width (thicker)
  • ✓ Bold, high-contrast stripes
Primary button (zebra-driven)

Contrast-Light vs Contrast-Dark

The two contrast theme variants differ only in their base surface color and text inversion:

Aspect contrast-light contrast-dark
Base background --cem-color-cyan-xl (#f1fefe, very light) --cem-color-cyan-xd (#001010, very dark)
Base text --cem-color-cyan-xd (very dark) --cem-color-cyan-xl (very light)
Zebra stripe colors Use light palette values (blue-l, purple-l, etc.) Use dark palette values (blue-d, purple-d, etc.)
Stripe visibility Bold colors on white canvas Bold colors on black canvas
State indication Both use same 4-strip zebra system

Implementation Example

[data-theme="contrast-light"],
[data-theme="contrast-dark"] {
    /* Neutralize all backgrounds to comfort */
    --cem-action-explicit-default-background: var(--cem-palette-comfort);
    --cem-action-explicit-hover-background: var(--cem-palette-comfort);
    --cem-action-explicit-active-background: var(--cem-palette-comfort);
    /* ... repeat for all intents and states ... */

    /* Increase zebra thickness */
    --cem-zebra-strip-size: 3px;

    /* Add 4th zebra layer */
    --cem-action-box-shadow:
        0 0 0 3px var(--cem-zebra-color-0),
        0 0 0 6px var(--cem-zebra-color-1),
        0 0 0 9px var(--cem-zebra-color-2),
        0 0 0 12px var(--cem-zebra-color-3);
}

/* Intent-specific zebra base colors */
[data-theme*="contrast"] .action.explicit {
    --cem-zebra-color-0: var(--cem-palette-creativity);
}
[data-theme*="contrast"] .action.primary {
    --cem-zebra-color-0: var(--cem-palette-trust);
}
[data-theme*="contrast"] .action.destructive {
    --cem-zebra-color-0: var(--cem-palette-danger);
}

/* State modifiers adjust zebra-color-0 intensity */
[data-theme*="contrast"] .action:hover {
    --cem-zebra-color-0: color-mix(
        in srgb,
        var(--cem-zebra-color-0) 60%,
        var(--cem-palette-comfort-text) 40%
    );
}
        

Accessibility Benefits

When to Use Contrast Themes

Contrast themes are recommended for:

Auto-activation: Contrast themes can be automatically enabled when the system reports high contrast preferences:

@media (prefers-contrast: more) {
    :root {
        /* Auto-switch to contrast theme */
        /* Respects prefers-color-scheme for light vs dark */
    }
}
        
Dimensional Tokens

Dimensional tokens control the physical appearance and depth of UI elements through border radius, shadows, and font weight. These tokens work consistently across all themes while adapting to specific theme requirements.

Bend/Border-Radius Tokens

Purpose: The bend token system provides consistent corner styling across all action elements and containers. The term "bend" reflects the visual metaphor of bending a flat surface to create rounded corners.

Bend Token Values

Three predefined bend styles are available, each serving different design needs:

--cem-bend-smooth — 0.5rem
Gentle rounded corners that soften the appearance without being overly rounded. This is the default bend style.
Use cases: Standard buttons, input fields, cards, panels
Visual effect: Professional, modern, approachable
Button with smooth corners
--cem-bend-round — 50cqh (50% container query height)
Fully rounded/pill-shaped corners. The cqh unit ensures the element becomes a perfect pill regardless of dimensions.
Use cases: Toggle buttons, chips, tags, floating action buttons
Visual effect: Playful, friendly, highly interactive
Note: Falls back to 50% in browsers without container query support
Pill-shaped button
--cem-bend-sharp — 0
No rounding, sharp 90-degree corners. Minimalist and geometric.
Use cases: Technical interfaces, data tables, terminal-style UIs, strict grid layouts
Visual effect: Precise, technical, no-nonsense
Sharp-cornered button

Usage Pattern

The --cem-bend token serves as the active bend value, defaulting to --cem-bend-smooth:

/* Default bend */
:root {
    --cem-bend-smooth: 0.5rem;
    --cem-bend-round: 50cqh;
    --cem-bend-sharp: 0;

    --cem-bend: var(--cem-bend-smooth);  /* Default */
}

/* Apply to action elements */
.action {
    --cem-action-border-radius: var(--cem-bend);
    border-radius: var(--cem-action-border-radius);
}
        

Changing Bend Style

Override the bend style at any scope using CSS classes or custom properties:

/* Via utility classes */
<button class="action primary cem-bend-round">Pill Button</button>
<button class="action primary cem-bend-sharp">Sharp Button</button>

/* Via CSS override */
.my-component {
    --cem-bend: var(--cem-bend-round);
}

/* Per element */
button.special {
    --cem-action-border-radius: 1rem;  /* Custom radius */
}
        

Bend Classes

Pre-defined utility classes for easy application:

Class Sets --cem-bend to Visual Result
.cem-bend-smooth var(--cem-bend-smooth) (0.5rem) Gentle rounded corners
.cem-bend-round var(--cem-bend-round) (50cqh) Fully rounded/pill shape
.cem-bend-sharp var(--cem-bend-sharp) (0) Sharp 90° corners

Visual Examples

Container Query Units

The 50cqh value for --cem-bend-round uses container query height units, making pill shapes automatically adapt:

/* Small button: 2em tall */
<button style="height: 2em; border-radius: 50cqh;">
    /* border-radius computes to 1em (50% of 2em) */
</button>

/* Large button: 4em tall */
<button style="height: 4em; border-radius: 50cqh;">
    /* border-radius computes to 2em (50% of 4em) */
</button>
        

Browser compatibility: Container query units are supported in Chrome 105+, Firefox 110+, Safari 16+. For older browsers, provide a fallback:

.action.round {
    border-radius: 50%;  /* Fallback */
    border-radius: 50cqh;  /* Modern browsers */
}
        

Design Principles

Box Shadow Tokens

Purpose: Box shadow tokens create depth and elevation for interactive elements. Shadows provide visual feedback for different states, making interactions more intuitive and responsive.

Shadow Token System

Four state-specific shadow tokens control the appearance of actions across different interaction states:

--cem-action-box-shadow (default)
Value: 0px 2px 1px -1px rgba(0, 0, 0, .2), 0px 1px 1px 0px rgba(0, 0, 0, .14), 0px 1px 3px 0px rgba(0, 0, 0, .12)
Purpose: Base shadow for resting state. Provides subtle elevation without being distracting.
Visual effect: Slight lift from the surface, approximately 2dp elevation (Material Design)
Layers: Three-layer shadow (ambient, penumbra, umbra) for realistic depth perception
--cem-action-box-shadow-hover
Value: 0px 4px 6px -2px rgba(0, 0, 0, .2), 0px 2px 2px 0px rgba(0, 0, 0, .14), 0px 2px 4px 0px rgba(0, 0, 0, .12)
Purpose: Elevated shadow on hover. Increases perceived elevation to signal interactivity.
Visual effect: Medium lift, approximately 4dp elevation
Transition: Smoothly animates from default shadow, creating a "raising" effect
--cem-action-box-shadow-active
Value: 0px 1px 1px -1px rgba(0, 0, 0, .2), 0px 0px 0px 0px rgba(0, 0, 0, .14), 0px 0px 0px 0px rgba(0, 0, 0, .12)
Purpose: Reduced shadow when pressed/clicked. Simulates button being pushed into the surface.
Visual effect: Minimal lift, approximately 1dp elevation
Feedback: Provides tactile-like visual feedback for press actions
--cem-action-box-shadow-pending
Value: 0px 2px 1px -1px rgba(0, 0, 0, .2), 0px 1px 1px 0px rgba(0, 0, 0, .14), 0px 1px 3px 0px rgba(0, 0, 0, .12) (same as default)
Purpose: Shadow during async operations. Maintains same elevation as default while background animates.
Visual effect: Consistent elevation during loading, focus on background animation

Shadow Progression

Shadows progress through three elevation levels as users interact:

Shadow State Progression
default   →   hover    →   active   →   pending
 (2dp)          (4dp)         (1dp)          (2dp)

  ▭            ▭            ▬            ▭
 subtle      elevated     pressed      loading
 lift        lift         down         (same as default)
            

CSS Implementation

Shadows are applied automatically based on element state:

/* Base shadow token */
.action {
    box-shadow: var(--cem-action-box-shadow);
    transition: box-shadow 0.2s ease-in-out;
}

/* State-specific shadows */
.action:hover,
.action.hover,
.action[hover] {
    --cem-action-box-shadow: var(--cem-action-box-shadow-hover);
}

.action:active,
.action.active,
.action[active] {
    --cem-action-box-shadow: var(--cem-action-box-shadow-active);
}

.action[pending],
.action.pending {
    --cem-action-box-shadow: var(--cem-action-box-shadow-pending);
}
        

Visual Comparison


2dp elevation

4dp elevation

1dp elevation

Contrast Theme Shadow Overrides (Zebra-Based)

In high-contrast themes (contrast-light, contrast-dark), shadows are replaced with zebra outlines for maximum visibility:

[data-theme*="contrast"] {
    /* Replace traditional shadows with zebra outlines */
    --cem-action-box-shadow:
        0 0 0 3px var(--cem-zebra-color-0),   /* Intent color stripe */
        0 0 0 6px var(--cem-zebra-color-1),   /* Focus stripe */
        0 0 0 9px var(--cem-zebra-color-2),   /* Selected stripe */
        0 0 0 12px var(--cem-zebra-color-3);  /* Target stripe */

    /* State changes affect zebra colors, not shadow depth */
    .action:hover {
        /* Zebra-color-0 intensifies, no shadow depth change */
        --cem-zebra-color-0: color-mix(
            in srgb,
            var(--cem-zebra-color-0) 60%,
            var(--cem-palette-comfort-text) 40%
        );
    }
}
        

Why zebra instead of shadows in contrast themes?

Material Design Elevation Mapping

Shadow tokens correspond to Material Design elevation levels:

Token Elevation (dp) Material Design Usage Component Examples
default 2dp Resting elevation for raised buttons Button, Card (resting)
hover 4dp Raised elevation on hover Button (hover), FAB (resting)
active 1dp Pressed state (lower than resting) Button (pressed)
pending 2dp Maintains resting elevation during async Button (loading)

Customizing Shadows

Override shadows at any scope for custom elevation or theme-specific effects:

/* Lighter shadows for dark theme */
[data-theme="dark"] {
    --cem-action-box-shadow: 0 2px 8px rgba(255, 255, 255, 0.1);
    --cem-action-box-shadow-hover: 0 4px 12px rgba(255, 255, 255, 0.15);
}

/* No shadows for flat design */
.flat-design {
    --cem-action-box-shadow: none;
    --cem-action-box-shadow-hover: none;
    --cem-action-box-shadow-active: none;
}

/* Custom elevation for specific component */
.floating-action-button {
    --cem-action-box-shadow: 0px 6px 10px -3px rgba(0, 0, 0, .2),
                              0px 4px 4px 0px rgba(0, 0, 0, .14),
                              0px 4px 8px 0px rgba(0, 0, 0, .12);  /* 6dp */
}
        

Shadow Anatomy (Three-Layer System)

Each shadow is composed of three layers for realistic depth:

Layer 1: Ambient Shadow (largest, softest)
Example: 0px 1px 3px 0px rgba(0, 0, 0, .12)
Simulates scattered light, creating overall depth perception
Layer 2: Penumbra Shadow (medium size, medium softness)
Example: 0px 1px 1px 0px rgba(0, 0, 0, .14)
Transition zone between light and dark, adds realism
Layer 3: Umbra Shadow (smallest, sharpest)
Example: 0px 2px 1px -1px rgba(0, 0, 0, .2)
Darkest part of shadow directly under element, creates sharp definition

Animation and Transitions

Shadows should transition smoothly between states:

.action {
    box-shadow: var(--cem-action-box-shadow);
    transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Faster transition on press for immediate feedback */
.action:active {
    transition-duration: 100ms;
}
        

Performance Considerations

Thickness/Font-Weight Mapping

Purpose: The thickness token system provides a semantic way to specify font weights, maintaining consistency with the 7-token design principle. These tokens map directly to CSS font-weight values.

Seven Thickness Levels

Based on the Typography Guide, seven standardized font weights provide sufficient variation for all UI needs:

Token Alternative Names CSS Value Sample Common Usage
xx-light Thin, Hairline 100 The quick brown fox Large headings, decorative text
x-light Extra Light, Ultra Light 200 The quick brown fox Secondary headings, captions
light Light 300 The quick brown fox Body text in light themes, metadata
normal Regular, Roman, Book 400 The quick brown fox Default body text, paragraphs
bold Medium, Semi-Bold 700 The quick brown fox Emphasis, buttons, headings, labels
x-bold Extra Bold 800 The quick brown fox Strong emphasis, primary headings
xx-bold Ultra Bold, Black, Heavy 900 The quick brown fox Maximum impact, hero headings

Design Rationale: Why 7 Levels?

The 7-token system aligns with human perception and cognitive limits:

Token Naming Convention

Token names follow a symmetric pattern around normal (400):

Lighter ←                → Heavier

xx-light   x-light   light   normal   bold   x-bold   xx-bold
  (100)     (200)    (300)    (400)   (700)   (800)    (900)

   ↓         ↓         ↓        ↓       ↓       ↓        ↓
 Thinnest                    Default             Thickest
        

Note: CSS provides values 100-900 in 100-unit increments, but the 7-token system skips 500 and 600 as they offer minimal perceptual difference from 400 and 700 respectively.

Usage in Theme System

While the theme system doesn't currently expose thickness tokens as CSS custom properties, the values guide font-weight choices throughout the components:

/* Example usage in components */
.cem-heading-1 {
    font-weight: 100;  /* xx-light for large display text */
}

.cem-heading-2 {
    font-weight: 300;  /* light for secondary headings */
}

body {
    font-weight: 400;  /* normal for body text */
}

.action {
    font-weight: 700;  /* bold for buttons */
}

strong, b {
    font-weight: 700;  /* bold for emphasis */
}

.cem-menu-item.selected {
    font-weight: 700;  /* bold for selected menu items */
}
        

Font Weight and Accessibility

Font weight impacts readability and should be chosen carefully:

Body Text
Use normal (400) or light (300) for optimal readability
Avoid weights below 300 for body text, especially at small sizes
In dark themes, consider light (300) to compensate for light-on-dark contrast
Headings
Larger headings (H1, H2) can use lighter weights (100-300) effectively
Smaller headings (H3-H6) should use bold (700) for distinction
Interactive Elements
Buttons and links benefit from bold (700) for clear affordance
Selected/active states should increase weight for visual feedback
Small Text
Text below 14px should avoid weights lighter than normal (400)
Thin fonts at small sizes can cause readability issues, especially for low-vision users

Font Weight Progression

Use weight changes to create visual hierarchy:

Example: Heading Hierarchy

Hero Title (100)

Section Heading (300)

Subsection (700)

Body paragraph text uses normal weight (400) for optimal readability.

Emphasized text uses bold weight (700) to stand out.

Variable Fonts

Modern variable fonts support continuous weight adjustment (100-900), but the 7-token system still provides meaningful checkpoints:

/* Variable font with custom weights */
@font-face {
    font-family: 'MyVariableFont';
    src: url('font.woff2') format('woff2-variations');
    font-weight: 100 900;  /* Supports full range */
}

/* Still use the 7 semantic tokens for consistency */
h1 { font-weight: 100; }  /* xx-light */
h2 { font-weight: 300; }  /* light */
p  { font-weight: 400; }  /* normal */
strong { font-weight: 700; }  /* bold */
        

Font Weight in Themes

Font weight may need adjustment across different themes:

/* Light theme: normal weight sufficient */
[data-theme="light"] body {
    font-weight: 400;
}

/* Dark theme: slightly lighter weight to compensate for contrast */
[data-theme="dark"] body {
    font-weight: 300;  /* or keep 400, font-dependent */
}

/* High contrast: use bolder weights for maximum clarity */
[data-theme*="contrast"] {
    body {
        font-weight: 400;  /* Ensure normal weight minimum */
    }
    strong, .emphasis {
        font-weight: 800;  /* Extra bold for clear distinction */
    }
}
        

Cross-Platform Considerations

Best Practices

Timing Tokens

Timing tokens control motion and animation behavior across the design system. Based on Material Design 3 motion principles, these tokens define how UI elements transition between states, ensuring consistent and meaningful animations that enhance user experience.

Duration Tokens

Purpose: Duration tokens define how long transitions take, based on user perception rather than arbitrary millisecond values. The naming reflects how users experience the timing.

Three Duration Levels

--cem-duration-instant — 50ms
Micro-interactions that should feel immediate. Used when feedback must appear to happen without delay.
Use cases: Button press feedback, checkbox toggles, ripple effects, input focus
Perception: "Did that just happen?" — so fast it feels instantaneous
--cem-duration-noticeable — 250ms
Mid-sized UI changes that users should register but not dwell on. The default duration for most transitions.
Use cases: Menu opening, dropdown expansion, tab switching, color transitions
Perception: "I saw that change" — noticeable but doesn't interrupt flow
--cem-duration-lingering — 300ms
Larger transitions or emotionally resonant changes that invite attention.
Use cases: Modal dialogs, page transitions, overlay appearances, route changes
Perception: "That felt significant" — draws attention to important changes

CSS Implementation

:root {
    --cem-duration-instant: 50ms;
    --cem-duration-noticeable: 250ms;
    --cem-duration-lingering: 300ms;

    /* Default duration for general use */
    --cem-duration: var(--cem-duration-noticeable);
}

/* Usage examples */
.action {
    transition-duration: var(--cem-duration-instant);  /* Quick feedback */
}

.menu {
    transition-duration: var(--cem-duration-noticeable);  /* Standard opening */
}

.modal {
    transition-duration: var(--cem-duration-lingering);  /* Significant appearance */
}
        

Reduced Motion Support

For users who prefer reduced motion, durations should be minimized:

@media (prefers-reduced-motion: reduce) {
    :root {
        --cem-duration-instant: 0ms;
        --cem-duration-noticeable: 0ms;
        --cem-duration-lingering: 0ms;
    }
}
        
Easing Tokens

Purpose: Easing tokens define acceleration curves that shape how animations feel. Named by consumer intent rather than technical implementation.

Easing Token System

Token Original MD3 CSS Value Intended Effect
--cem-easing-smooth standard (ease-in-out) cubic-bezier(0.4, 0, 0.2, 1) Balanced start and end; the "default" feel
--cem-easing-smooth-start standard.accelerate (ease-in) cubic-bezier(0.4, 0, 1, 1) Quickly ramps up; good for entrance animations
--cem-easing-smooth-end standard.decelerate (ease-out) cubic-bezier(0, 0, 0.2, 1) Slows into final state; good for exit animations
--cem-easing-highlighted emphasized (ease-in-out) cubic-bezier(0.2, 0, 0, 1) Adds drama to both start and end; draws attention
--cem-easing-highlighted-start emphasized.accelerate (ease-in) cubic-bezier(0.3, 0, 0.8, 0.15) Dramatic lift-off; emphasizes entry
--cem-easing-highlighted-end emphasized.decelerate (ease-out) cubic-bezier(0.05, 0.7, 0.1, 1) Dramatic finish; emphasizes exit
--cem-easing-uniform linear linear Constant speed; for mechanical or subtle motion
--cem-easing-classic legacy ease Classic curves retained for compatibility

Guidance for Use

Smooth variants
Use for everyday transitions where motion should feel natural and unobtrusive.
Highlighted variants
Use when motion's purpose is to draw the user's eye or emphasize a state change.
Start variants (-start)
Use for introducing new UI elements — they accelerate into view.
End variants (-end)
Use for dismissing UI elements — they decelerate as they exit.
Uniform
Use for neutral motion (progress bars, loading indicators) or when avoiding acceleration/deceleration.
Classic
Use only when maintaining legacy patterns or supporting older components.

CSS Implementation

:root {
    --cem-easing-smooth: cubic-bezier(0.4, 0, 0.2, 1);
    --cem-easing-smooth-start: cubic-bezier(0.4, 0, 1, 1);
    --cem-easing-smooth-end: cubic-bezier(0, 0, 0.2, 1);
    --cem-easing-highlighted: cubic-bezier(0.2, 0, 0, 1);
    --cem-easing-highlighted-start: cubic-bezier(0.3, 0, 0.8, 0.15);
    --cem-easing-highlighted-end: cubic-bezier(0.05, 0.7, 0.1, 1);
    --cem-easing-uniform: linear;
    --cem-easing-classic: ease;

    /* Default easing for general use */
    --cem-easing: var(--cem-easing-smooth);
}

/* Usage examples */
.action {
    transition: all var(--cem-duration) var(--cem-easing);
}

.menu-enter {
    animation: slideIn var(--cem-duration-noticeable) var(--cem-easing-smooth-start);
}

.menu-exit {
    animation: slideOut var(--cem-duration-noticeable) var(--cem-easing-smooth-end);
}

.attention-element {
    animation: pulse var(--cem-duration-lingering) var(--cem-easing-highlighted);
}
        
Spring Tokens

Purpose: Spring tokens define physics-based motion with damping and stiffness for expressive transitions. Springs create more natural, organic movement than easing curves alone.

Spring Token Dimensions

Springs are organized along three axes, creating a 2 × 2 × 3 matrix of combinations:

Axis Original MD3 CEM Token Description
Focus
(what property)
Spatial reposition Motions that move or resize elements (positions, layouts)
Effects highlight Non-spatial changes (opacity, color) that draw attention
Feel
(how lively)
Standard functional Little overshoot; for utilitarian interactions
Expressive delight Overshoot/bounce; adds emotional emphasis
Speed
(how long)
Fast instant Micro-interactions that shouldn't interrupt
Default noticeable User should register but not dwell
Slow lingering Large or emotionally resonant transitions

Spring Token Naming Convention

Tokens combine the three axes: --cem-spring-{focus}-{feel}-{speed}

/* Example spring token names */
--cem-spring-reposition-functional-instant
--cem-spring-reposition-functional-noticeable
--cem-spring-reposition-functional-lingering
--cem-spring-reposition-delight-instant
--cem-spring-reposition-delight-noticeable
--cem-spring-reposition-delight-lingering
--cem-spring-highlight-functional-instant
--cem-spring-highlight-functional-noticeable
--cem-spring-highlight-functional-lingering
--cem-spring-highlight-delight-instant
--cem-spring-highlight-delight-noticeable
--cem-spring-highlight-delight-lingering
        

When to Use Springs vs Easing

Use Springs for:
  • Drag-and-drop interactions that need physical feedback
  • Bounce effects on notifications or alerts
  • Pull-to-refresh gestures
  • Expressive micro-interactions
  • Elements that should "settle" into place
Use Easing for:
  • Simple fades and opacity changes
  • Color transitions
  • Legacy component support
  • Reduced-motion fallbacks
  • Cases where overshoot would be distracting

CSS Spring Implementation

CSS doesn't natively support spring physics, but can be approximated with keyframes or the Web Animations API:

/* CSS Variables for spring parameters */
:root {
    /* Functional springs (little overshoot) */
    --cem-spring-stiffness-functional: 300;
    --cem-spring-damping-functional: 25;

    /* Delight springs (expressive overshoot) */
    --cem-spring-stiffness-delight: 200;
    --cem-spring-damping-delight: 15;
}

/* Approximated spring animation via CSS */
@keyframes spring-bounce {
    0% { transform: scale(0.95); }
    40% { transform: scale(1.02); }
    60% { transform: scale(0.99); }
    80% { transform: scale(1.005); }
    100% { transform: scale(1); }
}

.spring-enter {
    animation: spring-bounce var(--cem-duration-noticeable) var(--cem-easing-highlighted-end);
}
        

JavaScript Spring Usage

For true spring physics, use the Web Animations API or a library:

// Using CSS custom properties for spring configuration
const springConfig = {
    reposition: {
        functional: { stiffness: 300, damping: 25 },
        delight: { stiffness: 200, damping: 15 }
    },
    highlight: {
        functional: { stiffness: 400, damping: 30 },
        delight: { stiffness: 250, damping: 18 }
    }
};

// Apply to animation library of choice
element.animate(
    { transform: ['translateX(0)', 'translateX(100px)'] },
    { duration: 250, easing: 'spring(1, 300, 25, 0)' }  // If supported
);
        
Combined Timing Usage

Purpose: This section shows how duration, easing, and spring tokens work together for cohesive motion design.

Standard Action Timing

.action {
    /* Combine duration and easing */
    transition:
        background-color var(--cem-duration-instant) var(--cem-easing-smooth),
        box-shadow var(--cem-duration-noticeable) var(--cem-easing-smooth),
        transform var(--cem-duration-instant) var(--cem-easing-smooth);
}

.action:hover {
    box-shadow: var(--cem-action-box-shadow-hover);
}

.action:active {
    transform: scale(0.98);
}
        

Modal/Overlay Timing

.modal {
    /* Entry: decelerate into view */
    animation: modalEnter var(--cem-duration-lingering) var(--cem-easing-highlighted-end);
}

.modal.closing {
    /* Exit: accelerate out */
    animation: modalExit var(--cem-duration-noticeable) var(--cem-easing-highlighted-start);
}

@keyframes modalEnter {
    from {
        opacity: 0;
        transform: scale(0.95) translateY(10px);
    }
    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

@keyframes modalExit {
    from {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
    to {
        opacity: 0;
        transform: scale(0.95) translateY(10px);
    }
}
        

Timing Token Summary

Category Tokens Purpose
Duration instant, noticeable, lingering How long animations take
Easing smooth, highlighted × start, end | uniform | classic Acceleration curves
Spring 2 focus × 2 feel × 3 speed Physics-based motion

Best Practices