Skip to main content

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 hardcode 24px drift 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, *-light tints, and *-rgb triplets.
  • 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.

Which name should I use?

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

ConcernFileDetail
Static base tokensflavor-starter/assets/css/base.css (:root)The always-present --flavor-* foundation.
Dynamic token generationflavor-starter/inc/design-system.phpDefaults, retrieval helpers, and the <style> emitter.
Front-end emissionflavor_ds_output_css()wp_head @ 98 (design-system.php:635)Prints <style id="flavor-design-system"> with the :root { … } DS tokens, aliases, generated h1h6 rules, and responsive-visibility / reorder media queries.
User CSS classesflavor_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 Fontsflavor_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 transitionsflavor_ds_output_page_transition()wp_footer @ 99 (design-system.php:933)Optional fade/slide page-load + navigation animation, driven by the page_transition option.
Front-end context

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

TokenDefaultNotes
--flavor-ds-primary#6366f1Also aliased to --flavor-primary.
--flavor-ds-primary-hover#4f46e5Aliased to --flavor-primary-dark.
--flavor-ds-secondary#8b5cf6
--flavor-ds-accent#8b5cf6
--flavor-ds-text#1f2937Body text.
--flavor-ds-text-light#6b7280Muted / secondary text.
--flavor-ds-heading#111827Heading 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-rgb99, 102, 241Comma-separated triplet, for rgba(var(--flavor-ds-primary-rgb), 0.1) patterns.
--flavor-ds-secondary-rgb139, 92, 246Same, 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

TokenDefaultNotes
--flavor-ds-font-bodysystem-ui stackFull font-stack string (Google font + category fallback, or the system stack).
--flavor-ds-font-headingsystem-ui stack
--flavor-ds-body-size16pxBase body font size. Also aliased to --flavor-text-base.
--flavor-ds-body-weight400
--flavor-ds-body-lh1.6Body line-height.
--flavor-ds-h{1..6}-size36 / 30 / 24 / 20 / 18 / 16 pxPer-heading size.
--flavor-ds-h{1..6}-weight700 / 700 / 600 / 600 / 600 / 600
--flavor-ds-h{1..6}-lh1.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 h1h6 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.

TokenDefault(at default base)
--flavor-ds-space-xs0.5rem8px
--flavor-ds-space-sm0.75rem12px
--flavor-ds-space-md1rem16px
--flavor-ds-space-lg1.5rem24px
--flavor-ds-space-xl2rem32px
--flavor-ds-space-2xl3rem48px
--flavor-ds-space-3xl4rem64px
--flavor-ds-space-4xl6rem96px

Breakpoints

TokenDefaultNotes
--flavor-ds-bp-tablet1024pxTablet max-width. Admin-configurable.
--flavor-ds-bp-mobile767pxMobile 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.

GroupTokensDefault range
Icon sizes--flavor-ds-size-icon-sm|md|lgclamp(16px, 2vw, 20px)clamp(22px, 3vw, 28px)
Button hit-targets--flavor-ds-size-btn-sm|md|lgclamp(32px, 4vw, 36px)clamp(44px, 5.5vw, 52px)
Logo sizes--flavor-ds-size-logo-sm|md|lgclamp(28px, 4vw, 40px)clamp(44px, 6vw, 60px)
Row heights--flavor-ds-size-row-topbar, --flavor-ds-size-row-maintopbar clamp(32px, 4vw, 40px), main clamp(56px, 8vw, 70px)
Fluid chrome text--flavor-ds-text-tiny|small|base|emphasisclamp(11px, 1.5vw, 12px)clamp(16px, 2.5vw, 18px)
Container max-widths--flavor-ds-container-xs|sm|md|lg|xl640px / 768px / 1024px / 1280px / 1440px
Chrome text ≠ content text

--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)

TokenValue
--flavor-ds-transition-fast150ms ease
--flavor-ds-transition-base250ms ease
--flavor-ds-transition-slow400ms ease
--flavor-ds-shadow-sm0 1px 2px rgba(0,0,0,0.05)
--flavor-ds-shadow-md0 2px 8px rgba(0,0,0,0.1)
--flavor-ds-shadow-lg0 4px 20px rgba(0,0,0,0.15)
note
There is no --flavor-ds-radius-* token

The 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):

HelperSignatureReturns
flavor_get_design_system()(bool $force_refresh = false): arrayAll DS values (defaults merged with saved), cached per request.
flavor_get_ds()(string $key, mixed $default = null): mixedA 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): arrayThe 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): stringA media-query expression (without @media) for 'mobile'|'tablet'|'mobile_min'|'tablet_min', honouring the admin ds_mobile_first preference.
flavor_get_design_system_palette()(): arrayThe 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:

  1. Admin UIAppearance → 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.
  2. Persistence — saved values are written as ds_* keys into the theme's design_system option group via flavor_set_option() → the {prefix}flavor_options custom table (theme Data Sovereignty — the theme owns its data; no wp_options blob).
  3. Retrievalflavor_get_design_system() reads each ds_* key with flavor_get_option() and merges it over flavor_get_design_system_defaults() (defaults win when a key was never customized), caching the result per request.
  4. Emissionflavor_ds_output_css() turns that merged set into the --flavor-ds-* custom properties (and the backward-compat aliases) in wp_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.