Files
Kaloyan Danchev 8a295c4acf Initial commit: @ampeco/design-tokens v1.0.0
Design token system for AMPECO Embedded Web Apps with light/dark themes,
CSS custom properties, and runtime theme application via applyTheme().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 19:34:08 +02:00

10 KiB

Architecture

Overview

@ampeco/design-tokens is the single source of truth for visual theming in the EWA (Embedded Web App). It bridges the AMPECO mobile app's design system to the web via CSS custom properties.

The package has two consumers:

  1. CSS -- imports css/variables.css for static defaults (colors, spacing, typography, radii, component sizes).
  2. JavaScript -- calls applyTheme() at runtime to set color tokens based on the active theme and any overrides from the mobile app.

Token Structure

Tokens are organized into two layers:

Themeable color tokens (JS-managed)

Defined in src/tokens.ts as the ThemeTokens interface. These are the tokens that change between light and dark mode. Each token has a corresponding CSS custom property name in TOKEN_CSS_MAP.

ThemeTokens key        CSS variable              Category
------------------------------------------------------------
btnPrimaryBg           --btn-primary-bg          Buttons
btnPrimaryText         --btn-primary-text        Buttons
btnPrimaryPressed      --btn-primary-pressed     Buttons
btnSecondaryBg         --btn-secondary-bg        Buttons
btnSecondaryBorder     --btn-secondary-border    Buttons
btnSecondaryText       --btn-secondary-text      Buttons
surfaceBg              --surface-bg              Surfaces
surfaceElevation1      --surface-elevation-1     Surfaces
surfaceElevation2      --surface-elevation-2     Surfaces
textBase               --text-base               Text
textSupport            --text-support            Text
textPlaceholder        --text-placeholder        Text
feedbackSuccess        --feedback-success        Feedback
feedbackSuccessSubtle  --feedback-success-subtle Feedback
feedbackError          --feedback-error          Feedback
feedbackErrorSubtle    --feedback-error-subtle   Feedback
feedbackWarning        --feedback-warning        Feedback
feedbackWarningSubtle  --feedback-warning-subtle Feedback
feedbackInfo           --feedback-info           Feedback
feedbackInfoSubtle     --feedback-info-subtle    Feedback
inputFill              --input-fill              Input
inputBorder            --input-border            Input
inputBorderActive      --input-border-active     Input
inputText              --input-text              Input
inputPlaceholder       --input-placeholder       Input
separator              --separator               Separator
statusAccepted         --status-accepted         Booking status
statusReserved         --status-reserved         Booking status
statusCompleted        --status-completed        Booking status
statusCancelled        --status-cancelled        Booking status
statusNoShow           --status-no-show          Booking status
statusFailed           --status-failed           Booking status

Static layout tokens (CSS-only)

Defined only in css/variables.css. These do not change between themes and are not managed by JS:

  • Spacing -- 9-step scale from --spacing-xxxs (2px) to --spacing-xxxl (64px)
  • Border radius -- 4-step scale: --radius-s (8px), --radius-m (12px), --radius-l (16px), --radius-xl (32px)
  • Typography -- 12 type styles, each with -size, -line-height, and -weight variables. Font family: Inter with system fallbacks.
  • Component sizes -- 4-step scale: --size-xs (14px) to --size-l (44px)

Theme System

Light and dark defaults

Two complete ThemeTokens objects are exported:

  • lightTheme (src/light.ts) -- Green primary (#00A573), white surfaces, black text.
  • darkTheme (src/dark.ts) -- Blue primary (#0066FF), black surfaces, white text.

Both share identical booking status colors (these are semantic and theme-independent).

Values are sourced from the AMPECO mobile app design system at: mobile-2/src/design_system/migration_file/colors/newColorsFormatExample.ts

CSS fallback

css/variables.css contains :root declarations matching the light theme. This ensures the page renders correctly even before JavaScript runs or if applyTheme() is never called.

AMPECO Mobile Token Mapping

The AMPECO mobile app sends design tokens via postMessage using its own naming convention. The mapping layer (src/tokenMapping.ts) translates these to EWA token keys.

Naming convention translation

AMPECO mobile name                     EWA token key
-----------------------------------------------------------
buttonsButtonPrimaryBackground    -->  btnPrimaryBg
buttonsButtonPrimaryText          -->  btnPrimaryText
buttonsButtonSecondaryBackground  -->  btnSecondaryBg
buttonsButtonSecondaryBorder      -->  btnSecondaryBorder
buttonsButtonSecondaryText        -->  btnSecondaryText
surfaceSurfaceBackground          -->  surfaceBg
surfaceSurfaceElevation1          -->  surfaceElevation1
surfaceSurfaceElevation2          -->  surfaceElevation2
textsTextBase                     -->  textBase
textsTextSupport                  -->  textSupport
textsTextPlaceholder              -->  textPlaceholder
feedbackColorFeedbackSuccess      -->  feedbackSuccess
feedbackColorFeedbackError        -->  feedbackError
feedbackColorFeedbackWarning      -->  feedbackWarning
feedbackColorFeedbackInfo         -->  feedbackInfo
inputFormsInputBorder             -->  inputBorder
inputFormsInputFill               -->  inputFill
inputFormsInputTextLabels         -->  inputText

The AMPECO mobile app sends a subset of tokens as AmpecoDesignTokens (all fields optional). The EWA has additional tokens (e.g. btnPrimaryPressed, feedbackSuccessSubtle, inputBorderActive, separator, all status* tokens) that are not mapped from mobile tokens and always come from the base theme.

mapAmpecoTokens()

function mapAmpecoTokens(tokens: AmpecoDesignTokens): Partial<ThemeTokens>

Iterates AMPECO_TO_EWA_MAP, looks up each AMPECO key in the input, and includes only truthy values in the result. Returns a partial token set suitable for spreading over a base theme.

How applyTheme() Works at Runtime

applyTheme() in src/apply.ts is the main entry point for theme application. Here is the exact sequence:

Step 1: Select base theme

const base = theme === 'DARK' ? darkTheme : lightTheme;

The theme parameter is 'LIGHT' or 'DARK' (matches AppTheme type, which corresponds to what the mobile app sends).

Step 2: Map and merge AMPECO overrides

const overrides = designTokens ? mapAmpecoTokens(designTokens) : {};
const merged: ThemeTokens = { ...base, ...overrides };

Mobile app tokens override the base theme. Tokens not provided by the mobile app retain their base theme defaults.

Step 3: Set data-theme attribute

document.documentElement.setAttribute('data-theme', theme.toLowerCase());

Sets data-theme="light" or data-theme="dark" on <html>. This enables CSS selectors like [data-theme="dark"] .card { ... } for theme-conditional styling that cannot be expressed with CSS custom properties alone.

Step 4: Write CSS custom properties

for (const [key, value] of Object.entries(merged)) {
  const cssVar = TOKEN_CSS_MAP[key as keyof ThemeTokens];
  if (cssVar && value) {
    document.documentElement.style.setProperty(cssVar, value);
  }
}

Every token in the merged set is written as an inline style on <html>, which overrides the :root defaults from css/variables.css.

Sequence diagram

Mobile App                EWA                       DOM
    |                      |                          |
    |-- postMessage ------>|                          |
    |   { theme, tokens }  |                          |
    |                      |-- applyTheme(theme, t) ->|
    |                      |   1. select base         |
    |                      |   2. map + merge         |
    |                      |   3. set data-theme      |
    |                      |   4. set CSS vars        |
    |                      |                          |
    |                      |                   [page re-renders
    |                      |                    with new colors]

CSS Variable Naming Conventions

All CSS custom properties follow a flat, kebab-case naming scheme with category prefixes:

Prefix Category Examples
--btn- Button colors --btn-primary-bg, --btn-secondary-text
--surface- Surface/background --surface-bg, --surface-elevation-1
--text- Text colors --text-base, --text-support
--feedback- Feedback/status colors --feedback-error, --feedback-info-subtle
--input- Form input --input-fill, --input-border-active
--separator Divider lines --separator
--status- Booking status --status-accepted, --status-no-show
--spacing- Spacing scale --spacing-xs, --spacing-xxl
--radius- Border radius --radius-s, --radius-xl
--font- Typography --font-h1-size, --font-body-medium-weight
--size- Component sizes --size-xs, --size-l

No BEM, no nesting, no component-scoped prefixes. Variables are global and intended for use across all components.

Build System

The package uses tsup (configured in tsup.config.ts):

  • Entry: src/index.ts
  • Formats: ESM (dist/index.js) + CJS (dist/index.cjs)
  • Types: dist/index.d.ts and dist/index.d.cts
  • Source maps: enabled

The CSS file is not processed by tsup. It ships as-is from css/variables.css and is exposed via the "./css" export map entry in package.json.

File Dependency Graph

src/index.ts  (barrel)
  |-- src/types.ts         AppTheme, AmpecoDesignTokens
  |-- src/tokens.ts        ThemeTokens, TOKEN_CSS_MAP
  |-- src/light.ts         lightTheme  (imports ThemeTokens from tokens.ts)
  |-- src/dark.ts          darkTheme   (imports ThemeTokens from tokens.ts)
  |-- src/tokenMapping.ts  AMPECO_TO_EWA_MAP, mapAmpecoTokens()
  |                          (imports ThemeTokens from tokens.ts,
  |                           AmpecoDesignTokens from types.ts)
  |-- src/apply.ts         applyTheme()
                             (imports everything above)

css/variables.css          standalone, no imports