The content of this file is used for supplementary documentation and generation of consumer-theme.css .
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:
xl ↔ xd (maximum contrast pairing)
l ↔ d or xd (high contrast pairing)
All pairings maintain ≥4.5:1 contrast ratio for text
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:
Emotional Palette : Branded colors map to emotional tokens via light-dark() function
State Variations : Colors are mixed using color-mix() to create state progressions
Accessibility : All color pairs maintain WCAG contrast ratios (≥4.5:1 for text, ≥3:1 for UI elements)
Material Design Compatibility : Tokens align with Angular Material and Material Design 3 color systems
Cyan Family
#f1fefe | Extra light cyan | Used for comfort palette (light scenes) | Maps to --ng-cyan-99
#00fbfb | Light cyan | Calm palette, creativity accent | Maps to --ng-cyan-90
#006a6a | Dark cyan | Calm palette (dark theme) | Maps to --ng-cyan-40
#001010 | Extra dark cyan | Used for comfort palette (dark scenes) | Maps to --ng-cyan-5
Blue Family
#faf9fd | Lightest blue | Comfort/trust emotion (light scene) | Maps to --ng-azure-neutral-98, --mat-sys-surface
#d7e3ff | Light blue | Trust palette, attention semantic | Maps to --ng-azure-90, --mat-sys-primary-container
#002f65 | Dark blue | Trust palette (dark theme) | Maps to --ng-azure-20
#1a1b1f | Darkest blue | Comfort/trust emotion (dark scene) | Maps to --ng-azure-neutral-10
Red Family
#ffb4ab | Extra light red | Danger palette (light theme) | Maps to --ng-error-80
#ba1a1a | Light red | Danger emotion, alert semantic | Maps to --ng-error-60, --mat-sys-error
#690005 | Dark red | Danger palette (dark theme) | Maps to --ng-error-20
#410002 | Extra dark red | Danger palette (dark theme, extreme) | Maps to --ng-error-10
Purple Family
#f3e5f5 | Extra light purple | Creativity palette (light theme) | Maps to --mat-purple-50
#e1bee7 | Light purple | Creativity emotion, brand-1 | Maps to --mat-purple-100
#6a1b9a | Dark purple | Creativity palette (dark theme) | Maps to --mat-purple-800
#4a148c | Extra dark purple | Creativity palette (dark theme, extreme) | Maps to --mat-purple-900
Orange/Yellow Family
#f0f070 | Extra light yellow | Enthusiasm palette (light theme) | Maps to --ng-yellow-80
#ffe082 | Light orange | Enthusiasm emotion, brand-2 | Maps to --mat-amber-200
#723600 | Dark orange | Enthusiasm hype (dark theme) | Maps to --ng-orange-30
#502400 | Extra dark orange | Enthusiasm palette (dark theme, extreme) | Maps to --ng-orange-20
Brown Family
#d7ccc8 | Extra light brown | Conservative palette (light theme) | Maps to --mat-brown-100
#a1887f | Light brown | Conservative emotion, brand-3 | Maps to --mat-brown-300
#4e342e | Dark brown | Conservative palette (dark theme) | Maps to --mat-brown-800
#3e2723 | Extra dark brown | Conservative palette (dark theme, extreme) | Maps to --mat-brown-900
Grey Family
extreme values for grey are white and black
#f1f1eb | Light grey | Conservative palette, neutral backgrounds | Maps to --ng-green-neutral-80
#1a1c18 | Dark grey | Conservative palette (dark theme) | Maps to --ng-green-neutral-10
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
Base comfort color | light-dark(cyan-xl, cyan-xd) | Main canvas/background
Comfort text color | light-dark(cyan-xd, cyan-xl) | Primary text on comfort backgrounds
Comfort extreme variant | light-dark(cyan-xd, cyan-xl) | Used in color-mix for state variations
Comfort extreme text | light-dark(cyan-xl, cyan-xd) | Text on extreme variant backgrounds
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
Base calm color | light-dark(cyan-l, cyan-d) | Foreground elements
Calm text color | Uses comfort-text | Text on calm backgrounds
Calm extreme variant | light-dark(cyan-d, cyan-l) | Inverted calm color
Calm extreme text | Uses comfort-text-x | Text on extreme calm backgrounds
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
Base trust color | light-dark(blue-l, blue-d) | Primary actions, highlights
Trust text color | light-dark(comfort-text, white) | Text on trust backgrounds
Trust extreme variant | light-dark(blue-d, blue-l) | Darker/lighter trust variation
Trust extreme text | Uses comfort-text-x | Text on extreme trust backgrounds
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
Base danger color | light-dark(red-l, red-d) | Error messages, destructive actions
Danger text color | light-dark(comfort-text-x, comfort-text) | Text on danger backgrounds
Danger extreme variant | light-dark(red-d, red-l) | Darker/lighter danger variation
Danger extreme text | Uses orange-xl | High contrast text on extreme danger backgrounds
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
Base creativity color | light-dark(purple-l, purple-d) | Brand-1, explicit actions
Creativity text color | Uses comfort-text | Text on creativity backgrounds
Creativity extreme variant | light-dark(purple-d, purple-l) | Darker/lighter creativity variation
Creativity extreme text | Uses comfort-text-x | Text on extreme creativity backgrounds
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
Base enthusiasm color | light-dark(orange-l, orange-d) | Brand-2, alternate actions
Enthusiasm text color | light-dark(comfort-text, white) | Text on enthusiasm backgrounds
Enthusiasm extreme variant | light-dark(orange-xd, orange-xl) | Darker/lighter enthusiasm variation
Enthusiasm extreme text | Uses comfort-text-x | Text on extreme enthusiasm backgrounds
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
Base conservative color | light-dark(grey-l, grey-d) | Brand-3, disabled states
Conservative text color | Uses comfort-text | Text on conservative backgrounds
Conservative extreme variant | light-dark(grey-d, grey-l) | Darker/lighter conservative variation
Conservative extreme text | Uses comfort-text-x | Text on extreme conservative backgrounds
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
button
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.
user explicitly/intentionally clicks or activates
default, confirmatory, implicit. Triggered by ENTER
integrated within a specific context, Toolbar/menu
triggering not usual alternative flow
risky, potentially harmful, requires caution
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
Customize Layout
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
Submit Form
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
⋮ More Options
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
Save as Draft
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
Delete Account
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:
Background-driven states: disabled, readonly, editable, required, default, indeterminate, hover, active, pending
Outline-driven states (zebra): selected, focus, target
Disabled
Readonly, underline is grayed out in Material Design
Editable, underlined in Material Design
Required, often marked by star suffix
button is outlined, input fields underlined in Material Design
No Value available, "select all" checkbox when not all of its sub-controls are checked.
Marked to outline the element is pointed by URL.
Outlined to reflect the input is focused. Buttons do not show the focus outline
Outline to reflect the focus is make by keyboard navigation, visible even in actions/buttons
Selected item(menu, radio), checked radio, etc.
Pressed action element state, darkest background with alternate text color to emphasize the action follows immediately.
Element state after active when element should not be used as the result of previous action is still in progress.
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:
--cem-action-{intent}-{state}-background
--cem-action-{intent}-{state}-text
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
Default (no zebra)
Focus (zebra-1 active)
Selected (zebra-2 active)
Target (zebra-3 active)
Contrast Theme Behavior
In high-contrast themes (contrast-light, contrast-dark):
Backgrounds are neutralized to base surface color
Zebra stripes become primary state indicator for ALL states
Zebra thickness increases (4 stripes instead of 3)
State color shifts to zebra stripe colors rather than backgrounds
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:
Browser overrides custom colors with system-defined high-contrast colors
All branded color tokens automatically resolve to appropriate system colors
Zebra outlines adapt to use system border colors for maximum visibility
Background color variations are flattened; state indication relies more on borders and outlines
color-mix() formulas are bypassed in favor of system color assignments
Advantages of Native Theme
Accessibility: Automatically respects user's OS-level accessibility preferences
Consistency: Matches system UI conventions users are familiar with
Performance: No complex color calculations or theme switching logic needed
Privacy: Respects privacy settings like reduced motion and high contrast without detection
Future-proof: Adapts to new system color schemes automatically
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:
Setting color-scheme: light instructs the browser that the page section uses a light color palette
Setting color-scheme: dark instructs the browser that the page section uses a dark color palette
Setting color-scheme: light dark allows the browser to choose based on prefers-color-scheme media query
The color-scheme property also affects form controls, scrollbars, and browser UI to match the theme
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:
Light theme: light backgrounds (-xl, -l) with dark text (-xd, -d)
Dark theme: dark backgrounds (-xd, -d) with light text (-xl, -l)
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
Primary Action
Delete
This box uses light-dark() to automatically adapt.
Advantages of Light/Dark Theme System
Automatic adaptation: Single token definition works for both light and dark modes
DRY principle: No need to duplicate CSS rules for each theme
Consistent logic: All state transitions use the same formulas regardless of theme
User preference: Automatically respects prefers-color-scheme media query
Reduced code: Eliminates thousands of lines of theme-specific overrides
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:
Neutralize backgrounds: All action backgrounds use the base comfort palette color (canvas)
Signal through stripes: State information is conveyed exclusively through zebra outline colors
Increase stripe count: Use 4-strip zebra pattern (vs 3-strip in normal themes) reflecting increased variations
Thicken outlines: Increase stripe width from 2px to 3-4px for improved perception
Preserve color semantics: Stripe colors match the intent's emotional palette colors
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:
Stripe-to-background contrast: ≥7:1 (AAA level) for all visible stripes
Adjacent stripe contrast: ≥3:1 between neighboring stripes
Text contrast: ≥7:1 against neutral background
State distinction: ≥3:1 contrast change between states (default vs hover)
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
Maximum contrast: Always uses extreme color pairs (AAA level, ≥7:1)
Clear state indication: Bold outlines are easier to perceive than subtle background changes
Consistent surface: Neutral backgrounds reduce visual noise and cognitive load
Multiple cues: 4 stripe layers provide redundant state information
Intent preservation: Color semantics maintained through zebra colors, not backgrounds
Low vision friendly: Thick outlines remain visible even with reduced acuity
Screen reader compatible: State information also conveyed through ARIA attributes
When to Use Contrast Themes
Contrast themes are recommended for:
Users with low vision or visual impairments
Environments with poor lighting conditions (very bright or very dim)
Users with light sensitivity (photophobia)
Compliance with WCAG AAA standards
Display devices with poor color reproduction
Users who enable "High Contrast" mode in their operating system
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
Smooth (0.5rem)
Round (50cqh)
Sharp (0)
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
Consistency: Use the same bend style throughout related components
Hierarchy: More important actions can use rounder corners to appear more friendly/inviting
Context: Match bend style to brand personality (playful vs. serious)
Accessibility: Corner radius doesn't affect accessibility, but maintain sufficient padding
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
Default
2dp elevation
Hover
4dp elevation
Active
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?
Visibility: Shadows can be difficult to perceive in high-contrast environments
Consistency: Zebra outlines maintain the same visual weight regardless of lighting
Accessibility: High-contrast outlines are more reliable for users with low vision
Forced colors: Browser high-contrast modes may override shadows, but outlines remain visible
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
Use box-shadow, not filter: drop-shadow() for better performance
Limit shadow layers: Three layers provide good realism without performance cost
Avoid excessive blur radius: Keep blur values under 10px for most cases
Hardware acceleration: Shadows are GPU-accelerated on modern browsers
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:
Pattern Recognition: 7 design tokens match human short-term memory capacity
Visual Distinction: Each level is perceptually different enough to be meaningful
Common Fonts: Most font families provide 7-9 weights, aligning with this system
Sufficient Range: From hairline (100) to black (900) covers all practical needs
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
macOS: System fonts render weights smoothly; thin weights (100-200) work well
Windows: ClearType rendering can make thin weights harder to read; consider 300+ minimum
Mobile: Smaller screens may benefit from slightly bolder default weights
Web fonts: Ensure font files for all 7 weights are loaded to prevent faux-bold rendering
Best Practices
Consistency: Use the same weight for similar content types (all buttons bold, all body text normal)
Hierarchy: Create clear distinction between heading levels through weight variation
Readability: Never sacrifice legibility for aesthetic—test with actual users
Performance: Limit font weight variants to 3-4 per page to reduce font file overhead
Fallbacks: Always specify a weight-agnostic fallback font family
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
Consistency: Use the same timing tokens for similar interactions across the application
Purpose: Choose tokens based on the intent of the motion, not arbitrary aesthetic preference
Accessibility: Always respect prefers-reduced-motion user preference
Performance: Animate only transform and opacity for best performance (GPU-accelerated)
Subtlety: Motion should enhance, not distract — when in doubt, use shorter durations