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 to extract design tokens from Figma files and translate them 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 tokens.


Figma Styles & Variables
|
v
packages/themes/src/
_base.css <-- Shared tokens (spacing, breakpoints, typography scale, z-index)
savoy-palace.css <-- Theme-specific tokens (colors, fonts, shadows)
royal-savoy.css
saccharum.css
... (8 theme files)
index.css <-- Imports _base + all themes

Key principle: Base tokens are shared across all sites. Theme tokens vary per site and are scoped under [data-theme="site-key"].


In the Figma file, tokens are typically found in:

Figma LocationToken Category
Local Styles > Colors--color-* tokens
Local Styles > Text--font-*, --text-* tokens
Local Styles > Effects--shadow-* tokens
Figma Variables (if used)Spacing, breakpoints, radii
Component padding/margin inspector--space-* references

For each hotel theme in Figma, extract the color palette:

Figma inspection:

  1. Select a primary-colored element in the Figma hotel design
  2. In the right panel, note the fill color hex value
  3. Map it to the correct token name

Token naming convention:

RoleToken NameExample Value (Savoy Palace)
Primary brand color--color-primary#1a365d
Primary lighter--color-primary-light#2a5a8e
Primary darker--color-primary-dark#0f2340
Secondary/accent--color-secondary#c9a96e
Secondary lighter--color-secondary-light#d4bc8e
Secondary darker--color-secondary-dark#b08f4f
Highlight accent--color-accent#e8d5b7
Page background--color-bg#faf9f7
Alt background--color-bg-alt#f2efe9
Dark background--color-bg-dark#1a1a1a
Surface (cards)--color-surface#ffffff
Body text--color-text#1a1a1a
Muted text--color-text-muted#6b7280
Inverse text (on dark bg)--color-text-inverse#ffffff
Text on primary--color-text-on-primary#ffffff
Text on secondary--color-text-on-secondary#1a1a1a
Borders--color-border#e5e2dc
Strong borders--color-border-strong#c9c4bb
Success--color-success#059669
Warning--color-warning#d97706
Error--color-error#dc2626

From Figma:

  1. Select a heading text element
  2. Note: font family, weight, size
  3. Map to tokens

Per-theme tokens (fonts vary by hotel):

TokenDescriptionExample
--font-headingHeading font stack'Playfair Display', serif
--font-bodyBody text font'Inter', sans-serif
--font-accentDecorative/accent font'Cormorant Garamond', serif
--font-weight-normalRegular weight400
--font-weight-mediumMedium weight500
--font-weight-semiboldSemibold weight600
--font-weight-boldBold weight700

Shared tokens (same across all themes) — in _base.css:

TokenValueFigma Size
--text-xs0.75rem12px
--text-sm0.875rem14px
--text-base1rem16px
--text-lg1.125rem18px
--text-xl1.25rem20px
--text-2xl1.5rem24px
--text-3xl1.875rem30px
--text-4xl2.25rem36px
--text-5xl3rem48px
--text-6xl3.75rem60px

Spacing is shared (not theme-specific). Extract from Figma’s padding/margin values:

TokenValueUsage
--space-10.25rem (4px)Tight inner spacing
--space-20.5rem (8px)Small gaps
--space-30.75rem (12px)Icon-text spacing
--space-41rem (16px)Standard padding
--space-61.5rem (24px)Section inner padding
--space-82rem (32px)Module padding (mobile)
--space-123rem (48px)Module padding (tablet)
--space-164rem (64px)Module padding (desktop)
--space-205rem (80px)Large section gaps
--space-246rem (96px)Hero padding

Rule: If a Figma spacing value doesn’t match the scale, round to the nearest token. Never introduce one-off spacing values.

Per-theme (shadow colors may use the primary color with opacity):

TokenExample (Savoy Palace)
--shadow-sm0 1px 2px rgba(26, 54, 93, 0.05)
--shadow-md0 4px 12px rgba(26, 54, 93, 0.08)
--shadow-lg0 8px 24px rgba(26, 54, 93, 0.12)

When Figma delivers a new hotel’s design system:

packages/themes/src/hotel-next.css
[data-theme="hotel-next"] {
/* -- Colors -- */
--color-primary: #...;
--color-primary-light: #...;
--color-primary-dark: #...;
--color-secondary: #...;
/* ... complete palette */
/* -- Typography -- */
--font-heading: '...', serif;
--font-body: '...', sans-serif;
/* ... weights */
/* -- Shadows -- */
--shadow-sm: ...;
--shadow-md: ...;
--shadow-lg: ...;
}

Then import in index.css:

@import './_base.css';
@import './savoy-signature.css';
@import './savoy-palace.css';
/* ... */
@import './hotel-next.css';

Register the font in apps/web/src/lib/fonts.ts:

import { NewFont } from 'next/font/google';
const newFont = NewFont({
subsets: ['latin'],
variable: '--font-heading',
display: 'swap',
});
const THEME_FONTS: Record<string, string> = {
'hotel-next': `${newFont.variable} ${inter.variable}`,
// ...
};

After extracting and implementing tokens for a theme:

  • All color tokens have sufficient contrast (4.5:1 for text, 3:1 for large text)
  • Font families load correctly (check Network tab — no 404s)
  • No layout shift from font loading (font-display: swap)
  • Theme file < 5KB gzipped
  • Storybook theme switcher shows the new theme
  • All existing components render correctly with the new theme
  • Tokens match Figma specifications exactly (spot-check 5 colors, 3 font sizes)

When inspecting a Figma element, translate properties:

Figma InspectorCSS Implementation
Fill: #1a365dbackground-color: var(--color-primary) (match to nearest token)
Text: Inter, 16px, Regularfont-family: var(--font-body); font-size: var(--text-base); font-weight: var(--font-weight-normal)
Padding: 32px 24pxpadding: var(--space-8) var(--space-6)
Gap: 16pxgap: var(--space-4)
Corner radius: 8pxborder-radius: var(--radius-md)
Shadow: 0 4px 12px rgba(...)box-shadow: var(--shadow-md)
Opacity text: 60%color: var(--color-text-muted) (use semantic token, not opacity)