Design System & Tokens
The Flavor Starter theme runs on a design-token layer: every visual and behavioural constant — a colour, a font size, a spacing step, a breakpoint, an animation duration, a shadow — is defined once as a CSS custom property and consumed everywhere through var(--token). Nothing in a template, module, or block hardcodes a raw #6366f1, 24px, or 250ms.
This page is for developers extending the theme — building a module, a page-builder block, or a child-theme customization. It covers the principle, the two token layers and where they live, the full token catalogue, how to consume tokens in your CSS, the PHP helpers, and how the tokens connect to the admin Flavor Options screen.
Why tokens (the principle)
This is Core-First engineering rule #4 applied to styling:
Every visual/behavioural constant is a token. No hardcoded px/rem/colours/breakpoints/durations/transitions in consumers. Tokens live centrally and are consumed by every surface — default chrome, custom builders, modules.
The payoff for you as an extension author:
- One source of truth. The primary colour is defined in exactly one place. A module that uses
var(--flavor-ds-primary)is automatically correct — and stays correct when the value changes. - Theme-wide restyle for free. When the store owner changes a colour, font, or spacing base in the admin, every token consumer re-styles across the whole site — including your module — with no extra code.
- Consistency by construction. Two modules that both use
var(--flavor-ds-space-lg)share the exact same spacing. Two modules that both hardcode24pxdrift apart the moment the scale changes.
If you find yourself typing a raw pixel value or hex colour into a module's CSS, stop: there is almost certainly a token for it. Hardcoded literals in a consumer are treated as design debt in this codebase.
Two token layers
There are two sets of custom properties, and understanding the split is the key to consuming them correctly.
Layer 1 — static base tokens (--flavor-*)
Defined in flavor-starter/assets/css/base.css under :root. These are always present on every page, are not admin-configurable, and provide the full foundational scale:
- Full colour palette + neutral gray scale (
--flavor-gray-50…--flavor-gray-900), semantic colours,*-lighttints, and*-rgbtriplets. - Type scale (
--flavor-text-xs…--flavor-text-5xl), font weights (--flavor-font-light…--flavor-font-bold), line-heights (--flavor-leading-*). - Static spacing scale (
--flavor-space-0…--flavor-space-16). - Border radii (
--flavor-radius-sm…--flavor-radius-2xl,--flavor-radius-full). - Static shadows (
--flavor-shadow-xs…--flavor-shadow-2xl) and transitions (--flavor-transition-fast|base|slow).
Layer 2 — dynamic Design System tokens (--flavor-ds-*)
Generated at request time by flavor_ds_output_css() (flavor-starter/inc/design-system.php:418) and printed into wp_head at priority 98, inside a <style id="flavor-design-system"> block. These are the admin-driven tokens: their values come from the store owner's choices in Flavor Options → Design System (see Connection to Flavor Options).
The same function also writes a set of backward-compatibility aliases that map key legacy --flavor-* names onto their --flavor-ds-* equivalents (design-system.php:563-588), for example:
:root {
--flavor-primary: var(--flavor-ds-primary);
--flavor-primary-dark: var(--flavor-ds-primary-hover);
--flavor-secondary: var(--flavor-ds-secondary);
--flavor-text: var(--flavor-ds-text);
--flavor-background: var(--flavor-ds-background);
--flavor-font-body: var(--flavor-ds-font-body);
/* …a curated subset, not the whole base palette… */
}
Why this matters: because of these aliases, a consumer that uses var(--flavor-primary) (the Layer 1 name) still picks up the admin-configured value. But only a curated subset of names is aliased — the gray scale, the static spacing scale, radii, and the static shadow/transition scales are not aliased and keep their base.css values.
Prefer the --flavor-ds-* name when a token exists in the Design System layer (colours, typography, DS spacing, chrome sizing, containers, DS transitions/shadows) — those are the admin-driven ones. Fall back to the --flavor-* base tokens for things the DS layer doesn't emit (gray scale, radii, the fine-grained --flavor-space-N scale). When in doubt, both resolve — the aliases keep legacy names working.
Where tokens live and how they are emitted
| Concern | File | Detail |
|---|---|---|
| Static base tokens | flavor-starter/assets/css/base.css (:root) | The always-present --flavor-* foundation. |
| Dynamic token generation | flavor-starter/inc/design-system.php | Defaults, retrieval helpers, and the <style> emitter. |
| Front-end emission | flavor_ds_output_css() → wp_head @ 98 (design-system.php:635) | Prints <style id="flavor-design-system"> with the :root { … } DS tokens, aliases, generated h1–h6 rules, and responsive-visibility / reorder media queries. |
| User CSS classes | flavor_ds_output_classes_css() → wp_head @ 97 (design-system.php:841) | Prints <style id="flavor-ds-classes"> for admin-defined reusable classes (.flavor-class-{slug}) with tablet/mobile overrides. |
| Google Fonts | flavor_ds_enqueue_google_fonts() → wp_enqueue_scripts @ 1 (design-system.php:719) | Enqueues the selected body/heading fonts + preconnect hints, only when Google Fonts is enabled. |
| Page transitions | flavor_ds_output_page_transition() → wp_footer @ 99 (design-system.php:933) | Optional fade/slide page-load + navigation animation, driven by the page_transition option. |
The <style id="flavor-design-system"> block is emitted on the front end (wp_head). Do not assume the same --flavor-ds-* tokens are present inside wp-admin — the block/Puck editor surfaces receive them through the builder's own enqueue. When you write CSS that must render identically in the editor preview and on the front end, provide a fallback (see Consuming tokens) so nothing breaks if a token is absent in one context.
Token catalogue
All tokens below are the --flavor-ds-* tokens emitted by flavor_ds_output_css(). Values shown are the defaults (flavor_get_design_system_defaults(), design-system.php:98), which mirror base.css :root so an un-customized site sees zero visual change.
Colours
| Token | Default | Notes |
|---|---|---|
--flavor-ds-primary | #6366f1 | Also aliased to --flavor-primary. |
--flavor-ds-primary-hover | #4f46e5 | Aliased to --flavor-primary-dark. |
--flavor-ds-secondary | #8b5cf6 | |
--flavor-ds-accent | #8b5cf6 | |
--flavor-ds-text | #1f2937 | Body text. |
--flavor-ds-text-light | #6b7280 | Muted / secondary text. |
--flavor-ds-heading | #111827 | Heading colour. |
--flavor-ds-background | #ffffff | |
--flavor-ds-link | #6366f1 | |
--flavor-ds-link-hover | #4f46e5 | |
--flavor-ds-success | #10b981 | |
--flavor-ds-warning | #f59e0b | |
--flavor-ds-error | #ef4444 | |
--flavor-ds-primary-rgb | 99, 102, 241 | Comma-separated triplet, for rgba(var(--flavor-ds-primary-rgb), 0.1) patterns. |
--flavor-ds-secondary-rgb | 139, 92, 246 | Same, for secondary. |
--flavor-ds-custom-1 … -8 | (unset) | Up to 8 store-defined custom colours; emitted only when the admin fills a hex. |
Colour tokens are emitted only when the stored value passes sanitize_hex_color().
Typography
| Token | Default | Notes |
|---|---|---|
--flavor-ds-font-body | system-ui stack | Full font-stack string (Google font + category fallback, or the system stack). |
--flavor-ds-font-heading | system-ui stack | |
--flavor-ds-body-size | 16px | Base body font size. Also aliased to --flavor-text-base. |
--flavor-ds-body-weight | 400 | |
--flavor-ds-body-lh | 1.6 | Body line-height. |
--flavor-ds-h{1..6}-size | 36 / 30 / 24 / 20 / 18 / 16 px | Per-heading size. |
--flavor-ds-h{1..6}-weight | 700 / 700 / 600 / 600 / 600 / 600 | |
--flavor-ds-h{1..6}-lh | 1.25 / 1.3 / 1.35 / 1.4 / 1.45 / 1.5 | |
--flavor-ds-h{1..6}-ls | (unset by default) | Letter-spacing (em). Only emitted when non-zero — the default is 0, so it is absent on a stock site. Reference it with a fallback: var(--flavor-ds-h1-ls, normal). |
The emitter also generates the actual h1–h6 rules that bind these tokens, so headings pick up admin typography automatically.
Spacing (DS scale)
Computed from a single base unit (ds_space_base_unit, default 0.25rem) × a fixed multiplier (flavor_ds_spacing_scale(), design-system.php:259). Change the base unit in admin and the whole scale rescales proportionally.
| Token | Default | (at default base) |
|---|---|---|
--flavor-ds-space-xs | 0.5rem | 8px |
--flavor-ds-space-sm | 0.75rem | 12px |
--flavor-ds-space-md | 1rem | 16px |
--flavor-ds-space-lg | 1.5rem | 24px |
--flavor-ds-space-xl | 2rem | 32px |
--flavor-ds-space-2xl | 3rem | 48px |
--flavor-ds-space-3xl | 4rem | 64px |
--flavor-ds-space-4xl | 6rem | 96px |
Breakpoints
| Token | Default | Notes |
|---|---|---|
--flavor-ds-bp-tablet | 1024px | Tablet max-width. Admin-configurable. |
--flavor-ds-bp-mobile | 767px | Mobile max-width. Admin-configurable. |
Because breakpoints are configurable, do not hardcode @media (max-width: 767px) in a module. Use the PHP helper flavor_ds_media_query() to generate the correct expression (see PHP helpers), or the ready-made responsive-visibility classes the emitter outputs: .flavor-hide-desktop, .flavor-hide-tablet, .flavor-hide-mobile, plus element reordering via data-order-tablet="N" / data-order-mobile="N" attributes.
Chrome sizing, fluid text, containers (RDSR / F60)
Fixed clamp()-based tokens for theme chrome (header, nav, logo, buttons). These are not admin-configurable — they scale fluidly between mobile and desktop without per-breakpoint media queries.
| Group | Tokens | Default range |
|---|---|---|
| Icon sizes | --flavor-ds-size-icon-sm|md|lg | clamp(16px, 2vw, 20px) → clamp(22px, 3vw, 28px) |
| Button hit-targets | --flavor-ds-size-btn-sm|md|lg | clamp(32px, 4vw, 36px) → clamp(44px, 5.5vw, 52px) |
| Logo sizes | --flavor-ds-size-logo-sm|md|lg | clamp(28px, 4vw, 40px) → clamp(44px, 6vw, 60px) |
| Row heights | --flavor-ds-size-row-topbar, --flavor-ds-size-row-main | topbar clamp(32px, 4vw, 40px), main clamp(56px, 8vw, 70px) |
| Fluid chrome text | --flavor-ds-text-tiny|small|base|emphasis | clamp(11px, 1.5vw, 12px) → clamp(16px, 2.5vw, 18px) |
| Container max-widths | --flavor-ds-container-xs|sm|md|lg|xl | 640px / 768px / 1024px / 1280px / 1440px |
--flavor-ds-text-* (fluid chrome strings like phone/email/badge counts) is a different scale from the typography tokens above. Use the typography tokens (--flavor-ds-body-size, --flavor-ds-h{n}-*) for content and headings; use --flavor-ds-text-* only for chrome micro-text.
Transitions & shadows (DS)
| Token | Value |
|---|---|
--flavor-ds-transition-fast | 150ms ease |
--flavor-ds-transition-base | 250ms ease |
--flavor-ds-transition-slow | 400ms ease |
--flavor-ds-shadow-sm | 0 1px 2px rgba(0,0,0,0.05) |
--flavor-ds-shadow-md | 0 2px 8px rgba(0,0,0,0.1) |
--flavor-ds-shadow-lg | 0 4px 20px rgba(0,0,0,0.15) |
--flavor-ds-radius-* tokenThe Design System layer does not emit radius tokens. For border-radius use the static base tokens from base.css: --flavor-radius-sm (4px), --flavor-radius-md (8px), --flavor-radius-lg (12px), --flavor-radius-xl (16px), --flavor-radius-2xl (24px), --flavor-radius-full (9999px). The same applies to the fine-grained --flavor-space-N scale and the gray palette — those live only in Layer 1.
Consuming tokens
The rule for any consumer — a module CSS file, a block renderer, a child-theme stylesheet — is: reference tokens, never literals.
In a module's or block's CSS
.my-module-card {
color: var(--flavor-ds-text);
background: var(--flavor-ds-background);
padding: var(--flavor-ds-space-md) var(--flavor-ds-space-lg);
border-radius: var(--flavor-radius-md); /* static layer — no DS radius token */
box-shadow: var(--flavor-ds-shadow-sm);
transition: transform var(--flavor-ds-transition-fast);
}
.my-module-card__cta {
background: var(--flavor-ds-primary);
color: #fff;
}
.my-module-card__cta:hover {
background: var(--flavor-ds-primary-hover);
}
/* Tint using the RGB triplet token */
.my-module-card--highlight {
background: rgba(var(--flavor-ds-primary-rgb), 0.08);
}
This mirrors real in-tree usage — e.g. inc/modules/eshops/product-faq/assets/product-faq.css styles its button with var(--flavor-ds-space-sm), var(--flavor-ds-space-lg) and var(--flavor-ds-transition-fast).
Provide a fallback for editor / early-load safety
Because the front-end token block is emitted in wp_head, and to stay robust in the editor or any context where a token might be absent, use the CSS fallback form:
.my-block { color: var(--flavor-ds-text, #1f2937); }
The fallback should match the token's default so a missing token degrades gracefully rather than rendering unstyled. Many built-in blocks use exactly this pattern (var(--flavor-primary, #6366f1)).
The inline-style rule
Per Core-First rule #4, an inline style="…" attribute emitted by a template, module or block should emit CSS custom properties, not layout literals. Pass values into CSS by setting a variable inline and consuming it from a stylesheet, rather than baking the literal into the attribute:
// Good — inline emits a token the stylesheet consumes:
printf('<div class="my-block" style="--my-block-gap: %s">', esc_attr($gap));
// Avoid — a raw layout literal hardcoded inline:
printf('<div class="my-block" style="gap: %dpx; color: #6366f1">', $gap);
This keeps the value in the token system (themeable, overridable) instead of freezing it into markup.
PHP helpers
For template, module, and block-renderer code that needs token values in PHP (rather than CSS):
| Helper | Signature | Returns |
|---|---|---|
flavor_get_design_system() | (bool $force_refresh = false): array | All DS values (defaults merged with saved), cached per request. |
flavor_get_ds() | (string $key, mixed $default = null): mixed | A single DS value. $key works with or without the ds_ prefix — flavor_get_ds('color_primary'). |
flavor_ds_spacing_scale() | (float $base_unit = 0.25): array | The named spacing scale — each step as ['rem'=>…, 'px'=>…, 'var'=>'--flavor-ds-space-…']. |
flavor_ds_breakpoints() | (): array | ['tablet'=>…, 'mobile'=>…, 'desktop_min'=>…, 'tablet_min'=>…] (px ints). |
flavor_ds_media_query() | (string $tier, ?string $direction = null): string | A media-query expression (without @media) for 'mobile'|'tablet'|'mobile_min'|'tablet_min', honouring the admin ds_mobile_first preference. |
flavor_get_design_system_palette() | (): array | The colour palette (name + var(...) + hex) used by the builder colour picker — including any custom colours. |
Example — generating a breakpoint-correct media query in PHP-built CSS instead of hardcoding a pixel value:
$mq = flavor_ds_media_query('tablet'); // e.g. "(max-width: 1024px)" — respects admin + mobile-first
printf("@media %s { .my-module { padding: var(--flavor-ds-space-sm); } }", $mq);
Connection to Flavor Options
The dynamic tokens are the runtime output of the store owner's admin choices. The data flow:
- Admin UI — Appearance → Flavor Options → Design System (subtabs Colors / Typography / Spacing), rendered by
inc/theme-options/tab-design-system.php. This is also where the Page Builder colour-picker palette comes from. - Persistence — saved values are written as
ds_*keys into the theme'sdesign_systemoption group viaflavor_set_option()→ the{prefix}flavor_optionscustom table (theme Data Sovereignty — the theme owns its data; nowp_optionsblob). - Retrieval —
flavor_get_design_system()reads eachds_*key withflavor_get_option()and merges it overflavor_get_design_system_defaults()(defaults win when a key was never customized), caching the result per request. - Emission —
flavor_ds_output_css()turns that merged set into the--flavor-ds-*custom properties (and the backward-compat aliases) inwp_head.
The practical consequence for your extension: you never read colours or fonts from the database. Consume var(--flavor-ds-…) in CSS (or flavor_get_ds() in PHP) and the store owner's admin choices — including any future change — flow into your module automatically.
Reusable CSS classes (admin-defined)
Beyond tokens, the Design System stores an optional set of reusable CSS classes (ds_css_classes) that store owners define in admin. Each becomes a .flavor-class-{slug} rule (with optional tablet/mobile overrides and single-level parent inheritance), emitted in wp_head at priority 97 by flavor_ds_output_classes_css(). Programmatically you can read the list with flavor_ds_get_classes() / flavor_ds_get_class_list(). These are primarily a builder feature; a module rarely needs them, but they exist in the same subsystem.
Related
- Creating a Page-Builder Block — blocks consume these tokens in their renderers.
- Module Development — module CSS follows the same token rule.
- Data Sovereignty — where the
ds_*values are stored.