Skip to content

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.

The primary palette is a vibrant Google Blue hue used for interactive elements, emphasis, and brand identity.

TokenValueUsage
--ts-ref-primary-50#e8f0feSubtle backgrounds, hover states
--ts-ref-primary-100#d2e3fcLight accents
--ts-ref-primary-200#aecbfaBorders, dividers
--ts-ref-primary-300#8ab4f8Secondary accents
--ts-ref-primary-400#669df6
--ts-ref-primary-500#4285f4Default primary
--ts-ref-primary-600#1a73e8Interactive elements (buttons, links)
--ts-ref-primary-700#1967d2Hover states
--ts-ref-primary-800#185abcActive/pressed states
--ts-ref-primary-900#174ea6High contrast text on light backgrounds

The secondary palette is a neutral teal hue used for complementary actions, secondary emphasis, and accent elements.

TokenValueUsage
--ts-ref-secondary-50#f0fdfaSubtle backgrounds, hover states
--ts-ref-secondary-100#ccfbf1Light accents
--ts-ref-secondary-200#99f6e4Borders, dividers
--ts-ref-secondary-300#5eead4Secondary accents
--ts-ref-secondary-400#2dd4bf
--ts-ref-secondary-500#14b8a6Default secondary
--ts-ref-secondary-600#0d9488Interactive elements (secondary buttons, links)
--ts-ref-secondary-700#0f766eHover states
--ts-ref-secondary-800#115e59Active/pressed states
--ts-ref-secondary-900#134e4aHigh contrast text on light backgrounds

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.

TokenValue (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 communicate meaning independent of the specific hue.

CategoryToken (500)Token (600)Usage
Success#22c55e#16a34aPositive outcomes, confirmations
Warning#f59e0b#d97706Caution, non-blocking issues
Danger#ef4444#dc2626Errors, destructive actions
Info#3b82f6#2563ebInformational, neutral highlights

Rather than using palette tokens directly, components use role-based tokens that describe the purpose of the color:

TokenLightDarkUsage
--ts-color-text-primaryneutral-900#f4f5f8Primary body text
--ts-color-text-secondaryneutral-700#cdd0d8Supporting text, descriptions
--ts-color-text-tertiaryneutral-500#8b8f98Placeholder text, hints
--ts-color-text-disabledneutral-400#636770Disabled labels
--ts-color-text-inverseneutral-0#0e1016Text on dark/light backgrounds
--ts-color-text-on-primarywhitewhiteText on primary color backgrounds

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.

TokenLightDarkUsage
--ts-color-text-on-primarywhitewhiteText on primary-filled backgrounds
--ts-color-text-on-secondarywhitewhiteText on secondary-filled backgrounds
--ts-color-text-on-successwhitewhiteText on success-filled backgrounds
--ts-color-text-on-warningneutral-900#0e1016Text on warning-filled backgrounds (dark text for contrast)
--ts-color-text-on-dangerwhitewhiteText on danger-filled backgrounds
--ts-color-text-on-infoneutral-900#0e1016Text on info-filled backgrounds (dark text for contrast)

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 TokenText TokenLight BGDark BG
--ts-color-primary-container--ts-color-on-primary-containerprimary-50rgba(26, 115, 232, 0.15)
--ts-color-secondary-container--ts-color-on-secondary-containersecondary-50rgba(13, 148, 136, 0.15)
--ts-color-success-container--ts-color-on-success-containersuccess-50rgba(34, 197, 94, 0.15)
--ts-color-warning-container--ts-color-on-warning-containerwarning-50rgba(245, 158, 11, 0.15)
--ts-color-danger-container--ts-color-on-danger-containerdanger-50rgba(220, 38, 38, 0.15)
--ts-color-info-container--ts-color-on-info-containerinfo-50rgba(59, 130, 246, 0.15)

Selected state tokens are used for highlighting active items in lists, tabs, and navigation.

TokenLightDarkUsage
--ts-color-bg-selectedprimary-50rgba(26, 115, 232, 0.12)Selected item background
--ts-color-bg-selected-hoverprimary-100rgba(26, 115, 232, 0.18)Hovered selected item background
--ts-color-text-selectedprimary-700primary-300Selected item text
--ts-color-border-selectedprimary-500primary-400Selected item border/indicator
TokenLightDarkUsage
--ts-color-bg-surface#fafbff#111318Default page/component background
--ts-color-bg-elevated#ffffff#1b1d24Elevated surfaces (cards, modals)
--ts-color-bg-subtle#f0f3ff#1e2029Subtle tinted backgrounds
--ts-color-bg-overlayrgba(0,0,0,0.45)rgba(0,0,0,0.65)Modal/drawer overlays
--ts-color-bg-disabledneutral-100#1e2029Disabled input backgrounds
--ts-color-bg-hoverneutral-50#1b1d24Generic hover background
TokenLightDarkUsage
--ts-color-border-defaultneutral-200#2a2d36Standard borders
--ts-color-border-subtleneutral-100#1e2029Dividers, section separators
--ts-color-border-strongneutral-300#3b3e48High contrast borders

Used by ts-alert and similar status components. These use low-opacity tints of the semantic color:

TokenLightDark
--ts-color-info-bgrgba(59, 130, 246, 0.06)rgba(59, 130, 246, 0.12)
--ts-color-success-bgrgba(34, 197, 94, 0.06)rgba(34, 197, 94, 0.12)
--ts-color-warning-bgrgba(245, 158, 11, 0.06)rgba(245, 158, 11, 0.12)
--ts-color-danger-bgrgba(220, 38, 38, 0.06)rgba(220, 38, 38, 0.12)

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:

TokenOpacityState
--ts-state-hover-opacity0.08 (8%)Hover
--ts-state-focus-opacity0.12 (12%)Focus
--ts-state-pressed-opacity0.12 (12%)Pressed / active
--ts-state-dragged-opacity0.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 placeholders pulse between two neutral tones to indicate loading content:

TokenLightDarkUsage
--ts-color-skeleton-baseneutral-200#2a2d36Skeleton shape fill
--ts-color-skeleton-pulseneutral-100#1e2029Pulse animation target

All color combinations used for text and interactive elements must meet WCAG 2.1 AA contrast requirements:

Element TypeMinimum Ratio
Normal text (< 18px)4.5:1
Large text (>= 18px or >= 14px bold)3:1
UI components and graphical objects3: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

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 #000000 for 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 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.