Get started

White-label design system: separate brand themes from product behavior

A durable white-label design system gives every tenant one product contract and supplies presentation through versioned brand profiles. Colors, type, spacing, and approved assets may vary by tenant. Permissions, workflows, content authority, accessibility obligations, and component behavior stay out of theme settings. The practical work is deciding where each requested difference belongs, then proving that the resulting profile works in a named product slice.

Updated October 28, 2026

Start with one product, not one component fork per customer

A white-label design system supports several customer identities through one shared product. Its useful form is a stable interface between product behavior and tenant-controlled presentation, not a folder of duplicated buttons with different hex values.

That distinction matters because customer requests often arrive in visual language. "Make checkout feel like our service" might mean approved colors and type. It might instead mean changing field order, removing a required disclosure, altering who can approve a payment, or replacing the checkout flow. Only the first category is ordinary theming.

Once you fork the component library for each tenant, that boundary disappears. Every security fix, keyboard correction, and new state must be repeated across branches. At the other extreme, arbitrary CSS or component properties turn theme configuration into an unofficial programming surface. Either approach makes it hard to tell which product each tenant is actually running.

White-label does not mean unlimited customization

If a tenant-controlled value can change task order, permissions, required content, data handling, or recovery behavior, it is no longer a harmless theme setting. Route it to the product owner before implementation.

Classify every requested difference before building it

Use the smallest layer that owns the requested change. Don't judge by whether the request looks easy. Ask what it changes: reusable values, component structure, application behavior, content authority, or contractual obligations.

Theme overrideControlled variantApplication policySeparate product system
ChangesApproved visual values or assetsKnown component structure or presentationWorkflow, content, permissions, or obligationsProduct model or obligations that cannot remain shared
ExamplesSemantic colors, font family, radius, logoCompact navigation, card density, branded header formApproval rules, required notices, feature accessDifferent core workflow or incompatible compliance regime
OwnerBrand profile owner within system constraintsDesign-system component ownerConsuming application ownerProduct and architecture owners
Acceptance questionDo roles resolve and render safely?Does the supported variant preserve behavior and states?Does the policy work for authorized users and conditions?Can one product contract still describe both offerings honestly?
Typical failureRaw values leak into componentsVariant becomes a tenant-specific forkTheme data silently controls behaviorShared code hides two incompatible products
Customization routing matrix

A theme override changes a value behind an existing semantic role. A controlled variant changes a component in a way the shared component explicitly supports. Application policy governs meaning and behavior. Use a separate product system when the supposed customization changes the product so deeply that a common contract becomes misleading.

Use five questions for disputed requests

  1. Does the request change only a value, or does it change structure?
  2. Does it alter what an action does, who may perform it, or what evidence must appear?
  3. Can the shared component support it as a named variant for more than one credible consumer?
  4. Would accepting it create a tenant-specific exception that must be patched independently?
  5. Can the same product acceptance criteria still apply after the change?

If only the value changes, start at the theme layer. If structure changes but behavior remains stable, consider a controlled variant. If meaning, authority, or obligations change, use application policy. If the common acceptance criteria no longer fit, stop calling the difference a theme.

Keep four authorities separate

A maintainable architecture has four authorities. Each answers a different question, and none should quietly absorb the others.

  1. 1

    1. Fixed product invariants

    Define behavior that every tenant receives: task flow, permission checks, data rules, required states, validation meaning, recovery, and accessibility obligations. A brand profile cannot override these.

  2. 2

    2. Semantic interface contract

    Define the stable roles components consume, such as background, foreground, primary action, destructive action, border, focus ring, body type, heading type, spacing steps, and radius roles. The contract names meaning rather than a tenant's raw values.

  3. 3

    3. Versioned brand profile

    Map one approved brand source into the permitted semantic roles and assets. Record constraints, fallbacks, compatibility, ownership, and evidence state beside the values.

  4. 4

    4. Tenant configuration

    Select an approved profile version and the bounded options that tenant may use. This layer identifies the tenant and selection. It should not become a second token system or an escape hatch into product behavior.

This split gives each correction a home. A wrong color mapping belongs to the profile. A button that bypasses a role belongs to the implementation. A missing error state belongs to the component contract. An unauthorized workflow change belongs to application policy.

A tenant theme should choose among approved visual decisions, not acquire authority over the product.

Consistency check · Ad-hoc colors

The same plan card, built two ways in Ambient Sage.

Drifting system

Pricing

Starter$19/mo

Everything a small team needs to ship a branded UI.

Consistent system

Pricing

Starter$19/mo

Everything a small team needs to ship a branded UI.

What to notice: Semantic roles keep components stable while tenant values change. Direct color use creates local drift.

Copy a versioned brand-profile contract

A token file can carry values, but it often doesn't record enough context to govern a tenant rollout. Keep a compact profile record beside the generated artifacts. The exact serialization matters less than preserving the fields and their authority.

brandProfile:
  id: tenant-acme
  version: 1.2.0
  status: candidate

  authority:
    source: approved-brand-source-v4
    approvedBy: brand-owner
    approvedAt: unresolved

  compatibility:
    productContract: ">=3.4 <4.0"
    semanticInterface: 2.1.0
    supersedes: 1.1.0

  overrides:
    color.primary:
      value: "candidate-value"
      allowed: true
      constraints:
        - must resolve in light and dark modes
        - must retain the primary-action meaning
      fallback: default-profile.color.primary
      consumers:
        - checkout.primary-action
        - account.save-action
      owner: design-system-team
      evidenceState: delivered-not-observed

    typography.body:
      family: "approved-family"
      weights: [400, 500, 600, 700]
      allowed: true
      constraints:
        - product controls loading policy
        - fallback rendering requires observation
      fallback: default-profile.typography.body
      consumers:
        - account-settings
        - checkout
      owner: web-platform-team
      evidenceState: mapping-pending

  assets:
    logo:
      source: approved-logo-asset
      fallback: default-product-logo
      constraints:
        - preserve accessible name
        - fit documented header bounds

  prohibitedOverrides:
    - permissions
    - validation semantics
    - required notices
    - workflow order
    - component event behavior

  release:
    fallbackProfile: default-profile@3.4.0
    rollbackTrigger: unresolved
    observationOwner: tenant-release-owner
    finalDisposition: pending
Copyable brand-profile contract. Candidate values and unresolved fields are intentional until the consuming team supplies evidence.

The record answers two questions a flat theme object cannot: is this override permitted, and has anyone observed it in the consuming product? A valid artifact may contain the value while the intended component still reads an old alias, a local hardcoded value, or the wrong profile version.

Reject by default

Reject unknown keys and invalid values. Use a known profile as the fallback instead of passing an incomplete object deeper into the interface. Keep that fallback visible in logs or release evidence so the team doesn't mistake a configuration defect for successful adoption.

Start with a complete upstream kit

A semantic kit can supply the brand-profile inputs: light and dark roles, typography, spacing, layout guidance, and agent-ready artifacts. The consuming product still owns mapping, validation, component behavior, and tenant acceptance.

Map brand inputs into semantic roles

Components should request meaning, not customer values. A button consumes a primary-action background and foreground pair. An error message consumes an error role. A card consumes surface, foreground, and border roles. Tenant-specific values enter through the profile and stop there.

Define separate light and dark values under the same role. Don't derive a dark profile by blindly inverting or darkening the light palette. The role stays stable, but its value may need independent selection. Destructive, error, warning, success, focus, selection, and chart roles should not be inferred from one accent color either.

Token specimen · real values

Ambient Sage

Live render

Ambient Sage's actual tokens — the same values its exports use.

Color tokensSemantic roles with HEX / HSL / CMYK

Color tokens

Ambient Sage
light · HEX · HSL · CMYK

Core

#F3F4EF

background

H 72 · C0, 0, 2, 4

#1A1C17

foreground

H 84 · C7, 0, 18, 89

#E5E6E0

card

H 70 · C0, 0, 3, 10

#ECEEE8

muted

H 80 · C1, 0, 3, 7

#D8D9D2

border

H 68.57 · C0, 0, 3, 15

Brand

#FEE951

primary

H 52.72 · C0, 8, 68, 0

#1A1C17

primary-fg

H 84 · C7, 0, 18, 89

#E5E6E0

secondary

H 70 · C0, 0, 3, 10

#F7E464

accent

H 52.24 · C0, 8, 60, 3

#FEE951

ring

H 52.72 · C0, 8, 68, 0

Semantic

#C0392B

destructive

H 5.64 · C0, 70, 78, 25

#FFFFFF

destructive-fg

H 0 · C0, 0, 0, 0

#2D7238

success

H 129.57 · C61, 0, 51, 55

#C97D12

warning

H 35.08 · C0, 38, 91, 21

#545651

muted-fg

H 84 · C2, 0, 6, 66

Charts

#FEE951

chart-1

H 52.72 · C0, 8, 68, 0

#4A8FD4

chart-2

H 210 · C65, 33, 0, 17

#6BBF8A

chart-3

H 142.14 · C44, 0, 28, 25

#E07498

chart-4

H 340 · C0, 48, 32, 12

#E8A24B

chart-5

H 33.25 · C0, 30, 68, 9

Type scaleHeading, body, and mono in the kit's fonts

Typography

Ambient Sage

Scale: compact-product

Density: balanced

Heading · Plus Jakarta Sans · 1.875rem

Ship beautiful product faster

Subheading · Plus Jakarta Sans · 1.375rem

A warm-sage neutral-surface mobile kit with a single vivid yellow accent, flat tonal cards, and oversized display numerals.

Body · Plus Jakarta Sans · 1rem

Ambient Sage uses a near-white warm-sage canvas (#f3f4ef) with card panels distinguished only by a tonal shift to #e5e6e0, never by shadows or borders. A single vivid yellow (#fee951) is the only saturated color and appears sparingly at component scale as orbs, button fills, and focus rings. Primary data values render as oversized bold hero numerals with a small superscript unit. Typography is a friendly rounded geometric (Plus Jakarta Sans) with no uppercase and no tight tracking, while JetBrains Mono is reserved for hex codes and technical strings. Generous rounding and luminance-only contrast give the whole system a calm, minimal feel.

Mono · JetBrains Mono · 0.8125rem

npx shadcn add ambientsage.json

Aa

Plus Jakarta Sans · Heading

400500600700

Aa

Plus Jakarta Sans · Body

400500600700

ABCDEFGHIJKLM NOPQRSTUVWXYZ

abcdefghijklmnopqrstuvwxyz

0123456789 & @ # % →

Radius & spacingCorner radius, elevation, and spacing steps

Tokens

Ambient Sage primitives
density: balanced

Radius scale

sm · 0.375rem
md · 0.75rem
lg · 1.25rem
xl · 1.75rem

Component radius

button
card
input

Elevation

level 1
level 2
level 3
level 4

Spacing · base 4px

1x
2x
3x
4x
6x
8x
Ambient Sage demonstrates the kind of upstream semantic, typography, and spacing decisions a brand profile can ingest. It does not prove a tenant implementation.

Treat non-color decisions according to their behavior

  • Typography: a profile may select approved families, roles, and available weights. The product still owns loading, fallbacks, language coverage, line wrapping, and component fit.
  • Spacing and radii: expose shared values only where products can tolerate them. Keep layout transitions and responsive conditions in the layout or component contract.
  • Logos and imagery: store them as governed assets with format, size, crop, alternative-text, and fallback requirements. They are not color tokens.
  • Layout: allow bounded layout choices only when the product explicitly supports them. Arbitrary rearrangement can change comprehension and task order.
  • Voice and required copy: route brand tone to content guidance, but keep legal, safety, transactional, and product-state messages under the responsible application owner.

Font declarations need particular care. A profile can name a family and weight, yet the browser may initially render a fallback or fail to obtain the file. Loading policy and fallback behavior therefore belong in the product acceptance scope, not in a claim that the theme has already succeeded.

Exports carry decisions but do not prove adoption

DTCG tokens, Tailwind configuration, shadcn delivery, and DESIGN.md handle different parts of the handoff. DTCG can carry portable token data. Tailwind can expose selected values to utilities or CSS variables. A shadcn registry can deliver theme material into a compatible project. DESIGN.md can explain intent, usage, and prohibitions to people and coding agents.

None of those outputs proves that the active tenant selected the intended version, the application mapped every role, components stopped using local values, or rendered states passed review. Keep the evidence states separate: approved source, delivered artifact, project mapping, named consumer adoption, and observed result.

Available is not implemented

An upstream kit may make semantic roles and exports available. Your application implements them only when it maps and consumes those artifacts. The tenant experience is populated and observed only when the selected profile reaches a running consumer under recorded conditions.

Run a failure-oriented tenant acceptance check

Start with one representative product slice that includes real content, a form, primary and secondary actions, validation, status feedback, navigation, and at least one destructive or irreversible-looking state. An account-settings or checkout slice is usually more informative than an isolated button gallery.

Write the expectations before rendering. Then record what you observe without turning blanks into passes. The following template is explicitly unexecuted. It keeps the expected behavior, observed evidence, correction owner, and disposition in one place.

tenantAcceptance:
  tenant: tenant-acme
  profileVersion: 1.2.0
  productVersion: 3.6.1
  representativeSlice: account-settings
  environment: unresolved
  executedAt: null
  overallDisposition: pending

  cases:
    - case: valid-profile-light
      expectation: approved light roles reach all named consumers
      observation: not-run
      evidence: null
      correctionOwner: tenant-release-owner
      disposition: pending

    - case: valid-profile-dark
      expectation: dark roles resolve without reusing unintended light values
      observation: not-run
      evidence: null
      correctionOwner: design-system-team
      disposition: pending

    - case: missing-required-role
      expectation: schema rejection or documented fallback, with the failure recorded
      observation: not-run
      evidence: null
      correctionOwner: configuration-platform
      disposition: pending

    - case: invalid-value
      expectation: invalid input cannot reach component styling
      observation: not-run
      evidence: null
      correctionOwner: configuration-platform
      disposition: pending

    - case: interaction-states
      expectation: focus, hover, disabled, destructive, and error states remain distinguishable
      observation: not-run
      evidence: null
      correctionOwner: component-owner
      disposition: pending

    - case: long-content
      expectation: long labels, validation text, and tenant name do not obscure controls
      observation: not-run
      evidence: null
      correctionOwner: product-team
      disposition: pending

    - case: font-loading
      expectation: loading and unavailable-font states remain readable and usable
      observation: not-run
      evidence: null
      correctionOwner: web-platform-team
      disposition: pending

    - case: fallback-profile
      expectation: known fallback loads, selection failure is observable, and behavior is unchanged
      observation: not-run
      evidence: null
      correctionOwner: tenant-release-owner
      disposition: pending
Unexecuted tenant acceptance record for one named product slice.

A useful disposition is specific. Accept means the recorded scope met its expectations. Revise means a correction is required but the rollout can remain contained. Block means keeping the profile active would violate a product invariant or required acceptance condition. "Looks good" is not a disposition because it says nothing about modes, fallback, or failure states.

Component specimen · Input

Ambient Sage

Live render

The input primitive in Ambient Sage, across 4 states.

Default

Focus

Disabled

Error

Enter a valid email address.

A single input exposes several theme obligations at once: foreground pairing, border treatment, focus visibility, disabled distinction, and error presentation.

Release profiles with compatibility and rollback records

Treat a brand-profile change as a release unit. A token diff can reach every tenant surface that consumes the role, while a new asset might fail only in a particular shell. Record the expected reach before rollout so the team can choose representative checks and identify the first divergent layer.

profileRelease:
  releaseId: tenant-acme-profile-1.2.0
  brandProfile: tenant-acme@1.2.0
  compatibleProduct: ">=3.4 <4.0"
  semanticInterface: 2.1.0
  migrationStatus: prepared

  rollout:
    scope: one-internal-tenant
    representativeSlice: account-settings
    startCondition: acceptance-record-ready
    expansionCondition: all-required-cases-accepted

  fallback:
    profile: default-profile@3.4.0
    activationOwner: tenant-release-owner
    dataMigrationRequired: false

  rollback:
    triggers:
      - schema rejection in active configuration
      - missing required semantic role
      - unreadable or unavailable primary task
      - unresolved focus, error, or destructive state
      - unsupported product-version pairing
    procedureReference: application-owned

  observation:
    status: not-started
    evidence: null
    firstDivergence: null
    correctionOwner: null

  closure:
    disposition: pending
    approvedBy: null
    closedAt: null
Source-to-screen release record linking compatibility, rollout, fallback, rollback, observation, and closure.

Stage the rollout narrowly. Validate the profile object first. Map it in a non-production or bounded environment, inspect the representative slice, and expand only after the required cases have dispositions. If a problem appears, find the earliest failing layer: source decision, generated artifact, product mapping, component consumption, tenant selection, or rendered behavior.

Rollback should select a known compatible profile, not reconstruct old values by hand. The trigger also needs to be concrete enough to act on. "Brand feels off" may justify review, but it is not an operational trigger. A missing required role, unusable primary action, unsupported product pairing, or failed fallback is.

Bounded example: Ambient Sage as upstream intake

Ambient Sage is a useful intake example because its public record contains more than a color pair. It publishes semantic light and dark roles, a compact-product typography scale, Plus Jakarta Sans for body and heading roles at weights 400, 500, 600, and 700, and JetBrains Mono for the mono role at weights 400, 500, and 700. Identity Forge also supports DESIGN.md, DTCG, Tailwind, and shadcn delivery as upstream artifacts.

Kit showcase · live surfaces

Ambient Sage

Live render

Ambient Sage rendered from its real tokens across 3 surfaces.

App dashboardProduct UI: sidebar, KPI cards, area chart, recent sales, and a transactions table.
Ambient SageOverview
Search anything⌘K
AS

Analytics

Revenue overview

See revenue and retention trends alongside account health.

Jan 1 to Jan 30, 2026
Overview
Analytics
Reports
Notifications

Active users

15.1k

2,491 new

+5%

MRR

$49.1k

Net of churn

+3%

Retention

89%

28-day window

+2%

NPS

69

1,204 replies

+3

Revenue

Last 12 months

$49.1k +18.2%

12m30d7d
JanFebMarAprMayJunJulAugSepOctNovDec

Acquisition

Goal completion

On track
78%of goal
Organic48%
Direct31%
Referral21%

Recent transactions

Latest activity across your workspace

View all
CustomerStatusDateAmount
AR

Alex Rivera

Founder & CEO

Paid2 min ago$1,999.00
MO

Mira Okonkwo

Head of Product

Pending1 hour ago$39.00
JF

Jonas Feld

Design Lead

Processing3 hours ago$299.00
Mobile appNative app screen on a device, themed end to end.
9:41@ambientsage

Today

Sana

AS

Total balance

$49.1k

+3% this month

Send
Scan
Cards
Save

Recent

See all
P

Incoming payout

Today · 9:24

+$2,400.00
F

Figma

Subscriptions

-$45.00
C

Client invoice

Yesterday · 18:02

+$1,850.00
N

Northwind Co.

Transfer

-$120.00
S

Spotify

Subscriptions

-$11.99
Component sheetButtons, inputs, badges, controls — all shadcn, all themed.
Ambient Sage UI

Every shadcn component, themed by this kit.

Buttons

Badges

Default
Secondary
Outline
SuccessWarning

Avatar / chips

AS
minimalwarm-neutralflat

Form

Controls

Feedback

Onboarding72%

Ship beautiful product faster

A warm-sage neutral-surface mobile kit with a single vivid yellow accent, flat tonal cards, and oversized display numerals.

Tabs

AccountTeamBilling

Manage your account settings and preferences.

Alert

Heads up

Your trial ends in 7 days. Upgrade to keep access.

Tooltip

Add to library
One published kit shown across several surfaces. Consistent previews show the kit's direction, not acceptance in a separate tenant product.

For a white-label product, those facts can populate the source side of a candidate brand profile. The consuming team still has to decide which product roles accept the values, whether every required semantic role exists, how fonts are delivered, which components consume the mapping, and whether the compact scale fits real content.

No multi-tenant result follows from the kit alone. Tenant selection, schema validation, product-version compatibility, component adoption, accessibility evaluation, rollout, and runtime observations remain unresolved until the application team records them. The boundary is clear: Identity Forge can supply implementation-ready brand decisions, while the product owns the tenant experience.

Test two genuinely different profiles before expanding

A system that works for one profile may simply be tailored to it. Choose two candidates that differ in meaningful ways, such as lightness structure, type metrics, spacing density, radius character, or accent behavior. Run both through the same representative slice and acceptance cases.

Record the first divergence instead of patching the screenshot. If a long label fails only with one type family, inspect the typography mapping, available weight, fallback, component width contract, and content constraint in that order. If a focus state disappears in dark mode, inspect the semantic role and component state mapping before adding a tenant-specific outline.

  1. Freeze the product version, semantic interface version, representative slice, and required cases.
  2. Load profile A, record every observation, owner, and disposition.
  3. Load profile B under the same conditions and repeat the record.
  4. Find the earliest layer where an expectation and observation diverge.
  5. Correct the owning layer, rerun the affected cases, and expand only after both profiles receive evidence-backed dispositions.

Sources