Color
Color in Tessera UI is organized into three layers: reference palettes (raw values), semantic roles (purpose-driven), and component tokens (per-component overrides). This architecture ensures consistent theming and painless dark mode support.
The palette uses a vibrant blue primary with cool blue-tinted neutrals, creating a modern, M3-inspired aesthetic with strong readability.
Primary Palette
Section titled “Primary Palette”The primary palette is a vibrant Google Blue hue used for interactive elements, emphasis, and brand identity.
| Token | Value | Usage |
|---|---|---|
--ts-ref-primary-50 | #e8f0fe | Subtle backgrounds, hover states |
--ts-ref-primary-100 | #d2e3fc | Light accents |
--ts-ref-primary-200 | #aecbfa | Borders, dividers |
--ts-ref-primary-300 | #8ab4f8 | Secondary accents |
--ts-ref-primary-400 | #669df6 | — |
--ts-ref-primary-500 | #4285f4 | Default primary |
--ts-ref-primary-600 | #1a73e8 | Interactive elements (buttons, links) |
--ts-ref-primary-700 | #1967d2 | Hover states |
--ts-ref-primary-800 | #185abc | Active/pressed states |
--ts-ref-primary-900 | #174ea6 | High contrast text on light backgrounds |
Secondary Palette
Section titled “Secondary Palette”The secondary palette is a neutral teal hue used for complementary actions, secondary emphasis, and accent elements.
| Token | Value | Usage |
|---|---|---|
--ts-ref-secondary-50 | #f0fdfa | Subtle backgrounds, hover states |
--ts-ref-secondary-100 | #ccfbf1 | Light accents |
--ts-ref-secondary-200 | #99f6e4 | Borders, dividers |
--ts-ref-secondary-300 | #5eead4 | Secondary accents |
--ts-ref-secondary-400 | #2dd4bf | — |
--ts-ref-secondary-500 | #14b8a6 | Default secondary |
--ts-ref-secondary-600 | #0d9488 | Interactive elements (secondary buttons, links) |
--ts-ref-secondary-700 | #0f766e | Hover states |
--ts-ref-secondary-800 | #115e59 | Active/pressed states |
--ts-ref-secondary-900 | #134e4a | High contrast text on light backgrounds |
Neutral Palette
Section titled “Neutral Palette”Neutrals form the backbone of the UI — text, backgrounds, borders, and surfaces. The palette carries a subtle cool blue tint for a modern, M3-aligned feel.
| Token | Value (Light) | Value (Dark) |
|---|---|---|
--ts-color-neutral-0 | #ffffff | #0e1016 |
--ts-color-neutral-50 | #f8f9fc | #14161c |
--ts-color-neutral-100 | #f0f1f5 | #1e2029 |
--ts-color-neutral-200 | #e1e3e8 | #2a2d36 |
--ts-color-neutral-300 | #c4c7cf | #3b3e48 |
--ts-color-neutral-400 | #9196a1 | #636770 |
--ts-color-neutral-500 | #737884 | #8b8f98 |
--ts-color-neutral-600 | #5a5f6a | #a8acb5 |
--ts-color-neutral-700 | #434751 | #cdd0d8 |
--ts-color-neutral-800 | #2c3038 | #e4e6ec |
--ts-color-neutral-900 | #1a1c23 | #f4f5f8 |
Semantic Colors
Section titled “Semantic Colors”Semantic colors communicate meaning independent of the specific hue.
| Category | Token (500) | Token (600) | Usage |
|---|---|---|---|
| Success | #22c55e | #16a34a | Positive outcomes, confirmations |
| Warning | #f59e0b | #d97706 | Caution, non-blocking issues |
| Danger | #ef4444 | #dc2626 | Errors, destructive actions |
| Info | #3b82f6 | #2563eb | Informational, neutral highlights |
Color Roles
Section titled “Color Roles”Rather than using palette tokens directly, components use role-based tokens that describe the purpose of the color:
Text Colors
Section titled “Text Colors”| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-text-primary | neutral-900 | #f4f5f8 | Primary body text |
--ts-color-text-secondary | neutral-700 | #cdd0d8 | Supporting text, descriptions |
--ts-color-text-tertiary | neutral-500 | #8b8f98 | Placeholder text, hints |
--ts-color-text-disabled | neutral-400 | #636770 | Disabled labels |
--ts-color-text-inverse | neutral-0 | #0e1016 | Text on dark/light backgrounds |
--ts-color-text-on-primary | white | white | Text on primary color backgrounds |
On-Color Tokens
Section titled “On-Color Tokens”On-color tokens provide accessible text for use on filled semantic backgrounds. Each token is tuned so that text meets WCAG AA contrast against its corresponding filled color.
| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-text-on-primary | white | white | Text on primary-filled backgrounds |
--ts-color-text-on-secondary | white | white | Text on secondary-filled backgrounds |
--ts-color-text-on-success | white | white | Text on success-filled backgrounds |
--ts-color-text-on-warning | neutral-900 | #0e1016 | Text on warning-filled backgrounds (dark text for contrast) |
--ts-color-text-on-danger | white | white | Text on danger-filled backgrounds |
--ts-color-text-on-info | neutral-900 | #0e1016 | Text on info-filled backgrounds (dark text for contrast) |
Container Tokens
Section titled “Container Tokens”Container tokens provide subtle background fills paired with high-contrast text for banners, chips, and status indicators. In dark mode, containers use low-opacity tints instead of the light palette step.
| Background Token | Text Token | Light BG | Dark BG |
|---|---|---|---|
--ts-color-primary-container | --ts-color-on-primary-container | primary-50 | rgba(26, 115, 232, 0.15) |
--ts-color-secondary-container | --ts-color-on-secondary-container | secondary-50 | rgba(13, 148, 136, 0.15) |
--ts-color-success-container | --ts-color-on-success-container | success-50 | rgba(34, 197, 94, 0.15) |
--ts-color-warning-container | --ts-color-on-warning-container | warning-50 | rgba(245, 158, 11, 0.15) |
--ts-color-danger-container | --ts-color-on-danger-container | danger-50 | rgba(220, 38, 38, 0.15) |
--ts-color-info-container | --ts-color-on-info-container | info-50 | rgba(59, 130, 246, 0.15) |
Selected State Tokens
Section titled “Selected State Tokens”Selected state tokens are used for highlighting active items in lists, tabs, and navigation.
| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-bg-selected | primary-50 | rgba(26, 115, 232, 0.12) | Selected item background |
--ts-color-bg-selected-hover | primary-100 | rgba(26, 115, 232, 0.18) | Hovered selected item background |
--ts-color-text-selected | primary-700 | primary-300 | Selected item text |
--ts-color-border-selected | primary-500 | primary-400 | Selected item border/indicator |
Background Colors
Section titled “Background Colors”| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-bg-surface | #fafbff | #111318 | Default page/component background |
--ts-color-bg-elevated | #ffffff | #1b1d24 | Elevated surfaces (cards, modals) |
--ts-color-bg-subtle | #f0f3ff | #1e2029 | Subtle tinted backgrounds |
--ts-color-bg-overlay | rgba(0,0,0,0.45) | rgba(0,0,0,0.65) | Modal/drawer overlays |
--ts-color-bg-disabled | neutral-100 | #1e2029 | Disabled input backgrounds |
--ts-color-bg-hover | neutral-50 | #1b1d24 | Generic hover background |
Border Colors
Section titled “Border Colors”| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-border-default | neutral-200 | #2a2d36 | Standard borders |
--ts-color-border-subtle | neutral-100 | #1e2029 | Dividers, section separators |
--ts-color-border-strong | neutral-300 | #3b3e48 | High contrast borders |
Feedback Backgrounds
Section titled “Feedback Backgrounds”Used by ts-alert and similar status components. These use low-opacity tints of the semantic color:
| Token | Light | Dark |
|---|---|---|
--ts-color-info-bg | rgba(59, 130, 246, 0.06) | rgba(59, 130, 246, 0.12) |
--ts-color-success-bg | rgba(34, 197, 94, 0.06) | rgba(34, 197, 94, 0.12) |
--ts-color-warning-bg | rgba(245, 158, 11, 0.06) | rgba(245, 158, 11, 0.12) |
--ts-color-danger-bg | rgba(220, 38, 38, 0.06) | rgba(220, 38, 38, 0.12) |
State Layer Opacity System
Section titled “State Layer Opacity System”Interactive elements use semi-transparent overlays (“state layers”) to communicate hover, focus, pressed, and dragged states. The opacity values are defined as tokens so that custom-themed components stay consistent:
| Token | Opacity | State |
|---|---|---|
--ts-state-hover-opacity | 0.08 (8%) | Hover |
--ts-state-focus-opacity | 0.12 (12%) | Focus |
--ts-state-pressed-opacity | 0.12 (12%) | Pressed / active |
--ts-state-dragged-opacity | 0.16 (16%) | Drag in progress |
State layers are applied by overlaying the component’s key color (e.g., primary, danger) at the specified opacity. This approach works in both light and dark mode because the overlay is relative to the underlying surface.
Skeleton / Loading Colors
Section titled “Skeleton / Loading Colors”Skeleton placeholders pulse between two neutral tones to indicate loading content:
| Token | Light | Dark | Usage |
|---|---|---|---|
--ts-color-skeleton-base | neutral-200 | #2a2d36 | Skeleton shape fill |
--ts-color-skeleton-pulse | neutral-100 | #1e2029 | Pulse animation target |
Accessibility
Section titled “Accessibility”All color combinations used for text and interactive elements must meet WCAG 2.1 AA contrast requirements:
| Element Type | Minimum Ratio |
|---|---|
| Normal text (< 18px) | 4.5:1 |
| Large text (>= 18px or >= 14px bold) | 3:1 |
| UI components and graphical objects | 3:1 |
Guidelines:
- Never rely on color alone to convey meaning — pair with icons, labels, or patterns
- Semantic colors (success, warning, danger) always include the status in text, not just the color
- Focus indicators use a 2px blue ring with sufficient contrast against all backgrounds
High-Contrast Mode
Section titled “High-Contrast Mode”For users who require maximum contrast ratios, Tessera UI provides a high-contrast theme:
<body data-theme="high-contrast"> <!-- All components use maximum-contrast colors --></body>How it works:
- Neutral scale is remapped to pure grays without a blue tint, maximizing contrast between steps
- Text uses pure black (
#000000) and secondary text uses#1a1a1a - Borders switch to
#000000for maximum visibility - Interactive colors are darkened to ensure at least 7:1 contrast against white backgrounds
- Shadows are replaced with solid borders (
0 0 0 1px #000000) so elevation remains visible regardless of display settings - Focus rings use a stronger 60% opacity for unambiguous visibility
High-contrast mode also responds to the forced-colors media query. See the Accessibility page for details on forced-colors support.
Dark Mode
Section titled “Dark Mode”Dark mode is activated by adding data-theme="dark" to any ancestor element:
<body data-theme="dark"> <!-- All Tessera UI components automatically adapt --></body>How it works:
- Reference tokens (Tier 1) are immutable — they never change
- Semantic tokens (Tier 2) are remapped to different values in
[data-theme="dark"] - Component tokens (Tier 3) inherit the changes automatically through the
var()chain - Dark mode neutrals maintain the cool blue tint for visual consistency with the light theme
This means zero component code changes are needed to support dark mode — it’s handled entirely at the token layer.