Skip to content

02 — Design Tokens Extraction from Figma

Dev Guide — Savoy Signature Hotels
PRD refs: 05_Design_System_and_Theming.md, A03_Design_Tokens.md


This guide defines how design tokens are extracted from the Figma 01_Savoy_library file and translated into the CSS variable system used by the Savoy platform. Design tokens are the foundation of the multi-site theming system — every visual property flows from Figma variables through a scripted extraction pipeline into CSS custom properties.


Figma 01_Savoy_library (file key VFdCObxYyZjSRFdK5pU1df)
└─ Native variables export (W3C <Mode>.tokens.json per collection)
|
v
scripts/figma-tokens/adapt-figma-export.mjs
(re-links aliases by targetVariableName, per-mode brand disambiguation,
value-integrity check)
|
v
scripts/figma-tokens/figma-variables.json ← committed dump (1 197 variables)
|
v
scripts/figma-tokens/generate-themes.mjs + config.mjs
(curation: skips, no rename rules — mechanical name normalisation only)
|
v
packages/themes/src/*.css ← CANONICAL CSS (hand-maintained after first run)
|
v
scripts/figma-tokens/tokens-manifest.json ← feeds Storybook Foundations pages

Lifecycle rule: After the initial PR that introduced this pipeline, the CSS files in packages/themes/src/ are the canonical source of truth and are hand-maintained. The dump + generator scripts remain as the extraction record and a re-run tool only. If the generator is ever re-run, run Prettier over the output before diffing — pnpm lint-staged reformats quotes etc. on commit.

The Figma library (01_Savoy_library, file key VFdCObxYyZjSRFdK5pU1df) exposes these collections:

CollectionVariablesModesPurpose
Alias645Savoy Palace, Saccharum, Calheta Beach, Next, Signature, The Reserve, Royal SavoyPer-hotel resolved tokens — THE layer new code consumes
Responsive190Desktop, MobileText sizes, paddings, layout, radius-containers (responsive overrides)
Brand - Global99(single)Colour palettes + 41-step scale/* spacing primitives
Brand - Savoy PalaceBrand - Hotel Next31–43 each(single per brand)Brand colours + font family/weight strings
color12ORPHAN — referenced by nothing; skip; flagged for deletion in Figma

Note: There is no “Gardens” mode in the Alias collection — gardens.css is a stub that inherits Signature values. It will be replaced when a Gardens design is delivered.

Files are imported in this order in packages/themes/src/index.css:

FileLayerSelectorWhat it contains
_reset.css, _fonts.cssReset + @font-face(global)Browser normalisation; @font-face declarations — unchanged
_primitives.cssPrimitives:rootBrand - Global: colour palettes + --scale-* spacing steps
_responsive.cssResponsive:root (mobile) + @media (min-width: 1024px) (desktop)Text sizes, paddings, layout widths, radius-containers
_base.cssCode-only:rootTokens with no Figma counterpart: --z-*, --transition-*, --container-*, --header-height
&lt;theme&gt;.css × 8Alias / theme[data-theme="&lt;site-key&gt;"]Brand primitives + all 645 alias tokens resolved for that hotel’s mode — new code must consume tokens from this layer
_compat.cssCompat shim:rootLegacy pre-Figma names (--color-primary, --space-*, --text-*, module tokens) aliased to primitives or kept literal — FROZEN; existing modules only; never consume in new code

Theme applied via <html data-theme="{siteKey}"> — zero JS overhead.


The extraction was performed as a one-shot run on 2026-06-11. The steps below document what was done and how to repeat it if Figma variables change significantly.

  1. Open 01_Savoy_library in Figma desktop (file key VFdCObxYyZjSRFdK5pU1df)
  2. Use the native Figma variables export — produces zips of W3C &lt;Mode&gt;.tokens.json per collection
  3. Place the unzipped JSONs where adapt-figma-export.mjs expects them (see the script’s inline comments for paths)
Terminal window
node scripts/figma-tokens/adapt-figma-export.mjs

The adapter:

  • Re-links aliases by targetVariableName (the Figma-internal reference)
  • Applies per-mode brand disambiguation (each hotel mode resolves to the correct Brand - * collection)
  • Runs a value-integrity check and warns on broken references
  • Writes scripts/figma-tokens/figma-variables.json (1 197 variables, committed)
Terminal window
node scripts/figma-tokens/generate-themes.mjs

Uses scripts/figma-tokens/config.mjs for curation (collections to skip, no rename rules). Applies mechanical name normalisation only (see §3.4).

Unit-tested utilities live in scripts/figma-tokens/lib/. Run tests with:

Terminal window
node --test scripts/figma-tokens/lib/*.test.mjs

Names are derived mechanically from the Figma variable path — zero manual rename rules:

RuleDetail
LowercaseAll path segments converted to lowercase
colour/colourscolorBritish spelling normalised
Spaces and underscores → -All other non-alphanumeric chars stripped
Segments joined with -Full path becomes the property name

Examples:

Figma pathCSS custom property
colours/palace gold/500--color-palace-gold-500
colour/surface/background/A--color-surface-background-a
scale/600 (24)--scale-600-24
text sizes/display xl/font size--text-sizes-display-xl-font-size

Rule: The name shown in Figma Dev Mode IS the CSS custom property (after normalisation). Do not invent or shorten names.

scripts/figma-tokens/suggest-compat.mjs generated the initial _compat.css draft. The file is now hand-maintained and FROZEN — do not regenerate it without a deliberate Phase 2 decision.

The former §3.x “hand-inspection” tables (color roles, Figma inspector steps) described a manual process that no longer applies. They have been removed. All token values now originate from figma-variables.json via the generator.


DoDo not
Consume Alias tokens from &lt;theme&gt;.css (e.g. --color-surface-background-a)Consume _compat.css names (--color-primary, --space-*) in new code
Consume Brand - Global primitives from _primitives.css where semantically appropriate (e.g. --scale-*)Reference hardcoded colour values
Consume Responsive tokens from _responsive.css for text sizes, paddings, radiiInvent one-off values not in Figma
Use code-only tokens from _base.css for z-index, transitions, container layoutMix compat and alias names in the same new file

4.2 Token Prefix Reference (New Architecture)

Section titled “4.2 Token Prefix Reference (New Architecture)”
PrefixLayerPurposeExample
--color-*Primitives + AliasColour palette + semantic surface/text/border aliases--color-surface-background-a, --color-palace-gold-500
--scale-*Primitives41-step spacing scale (4px base) from Brand - Global--scale-600-24
--text-sizes-*ResponsiveResponsive type metrics per role (font size / line height / letter spacing), desktop + mobile modes--text-sizes-display-xl-font-size, --text-sizes-body-m-font-size
--paddings-*ResponsiveResponsive padding values--paddings-lateral-small
--layout-*ResponsiveResponsive column/layout widths--layout-2-columns
--radius-containers-*ResponsiveContainer border-radius (responsive)--radius-containers-palace-op2-top-left
--fonts-*AliasFont family / weight strings per hotel--fonts-h1-font, --fonts-h1-weight
--appearance-*AliasPer-hotel social network show/hide flags (1px = show, 0 = hide) — controls which social icons render per site--appearance-socialmedia-facebook
--z-*_base.css (code-only)Z-index scale--z-header, --z-modal, --z-toast
--transition-*_base.css (code-only)Duration scale--transition-fast, --transition-normal, --transition-slow
--container-*_base.css (code-only)Layout container sizing/padding--container-max, --container-padding
--header-height_base.css (code-only)Fixed header height for offset calculations--header-height

Legacy compat names (--color-primary, --font-heading, --font-body, --space-*, --text-xs--text-6xl, --radius-*, --shadow-*, --leading-*) survive in _compat.css for existing modules only. They carry TODO(phase2) comments and will be pruned in the Phase 2 module audit (P894-T769). Never use them in new code.

4.3 Figma-to-CSS Workflow for New Features

Section titled “4.3 Figma-to-CSS Workflow for New Features”
  1. Inspect the element in Figma Dev Mode — the variable name shown IS the CSS custom property (after normalisation)
  2. Confirm the property exists in the generated CSS: grep -r '&lt;property&gt;' packages/themes/src/
  3. Use it via var(--property-name) — no hardcoding, no compat names

packages/themes/src/
_reset.css ← Browser reset
_fonts.css ← @font-face declarations
_primitives.css ← :root — colour palettes + --scale-* spacing
_responsive.css ← :root mobile + @media (min-width:1024px) desktop overrides
_base.css ← Code-only tokens: --z-*, --transition-*, --container-*, --header-height
savoy-signature.css ← [data-theme="savoy-signature"]
savoy-palace.css ← [data-theme="savoy-palace"]
royal-savoy.css ← [data-theme="royal-savoy"]
saccharum.css ← [data-theme="saccharum"]
the-reserve.css ← [data-theme="the-reserve"]
calheta-beach.css ← [data-theme="calheta-beach"]
gardens.css ← [data-theme="gardens"] — STUB (inherits Signature; replace when design delivers)
hotel-next.css ← [data-theme="hotel-next"]
_compat.css ← FROZEN legacy compat shim — existing modules only
index.css ← Imports in the order above
tokens-manifest.json ← Generated; feeds Storybook Foundations (Colors/Typography/Spacing)

  1. New code consumes Alias tokens and primitives only_compat.css is a frozen pruning backlog targeted by the Phase 2 module audit (P894-T769).
  2. gardens.css is a stub inheriting Signature values — no Gardens design exists yet. Replace when design delivers.
  3. Fonts are Figma variables--fonts-* alias tokens carry family/weight strings per hotel. Legacy --font-heading/--font-body survive in compat until Phase 2.
  4. tokens-manifest.json (generated alongside CSS) feeds the Storybook Foundations pages (Colors, Typography, Spacing). Do not edit it by hand.
  5. Old --space-* and --text-* scales survive in compat only — the Figma spacing primitives are --scale-* and responsive sizes are --text-sizes-*.

After any theme update:

  • Token names match Figma Dev Mode (normalisation applied correctly)
  • All colour tokens have sufficient contrast (4.5:1 for text, 3:1 for large text)
  • No hardcoded values in theme CSS — only var(--token) references
  • Font families load correctly (check Network tab — no 404s)
  • No layout shift from font loading (font-display: swap)
  • Theme file renders correctly in Storybook theme switcher
  • All existing modules render correctly with updated theme
  • tokens-manifest.json updated if primitive/responsive tokens changed
  • Unit tests pass: node --test scripts/figma-tokens/lib/*.test.mjs