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 override | Controlled variant | Application policy | Separate product system | |
|---|---|---|---|---|
| Changes | Approved visual values or assets | Known component structure or presentation | Workflow, content, permissions, or obligations | Product model or obligations that cannot remain shared |
| Examples | Semantic colors, font family, radius, logo | Compact navigation, card density, branded header form | Approval rules, required notices, feature access | Different core workflow or incompatible compliance regime |
| Owner | Brand profile owner within system constraints | Design-system component owner | Consuming application owner | Product and architecture owners |
| Acceptance question | Do 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 failure | Raw values leak into components | Variant becomes a tenant-specific fork | Theme data silently controls behavior | Shared code hides two incompatible products |
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
- Does the request change only a value, or does it change structure?
- Does it alter what an action does, who may perform it, or what evidence must appear?
- Can the shared component support it as a named variant for more than one credible consumer?
- Would accepting it create a tenant-specific exception that must be patched independently?
- 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.
A maintainable architecture has four authorities. Each answers a different question, and none should quietly absorb the others.
- 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. 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. 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. 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.
Pricing
Everything a small team needs to ship a branded UI.
Pricing
Everything a small team needs to ship a branded UI.
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: pendingThe 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 renderAmbient Sage's actual tokens — the same values its exports use.
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: pendingA 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 renderThe input primitive in Ambient Sage, across 4 states.
Default
Focus
Disabled
Error
Enter a valid email address.
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: nullStage 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 renderAmbient Sage rendered from its real tokens across 3 surfaces.
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.
- Freeze the product version, semantic interface version, representative slice, and required cases.
- Load profile A, record every observation, owner, and disposition.
- Load profile B under the same conditions and repeat the record.
- Find the earliest layer where an expectation and observation diverge.
- Correct the owning layer, rerun the affected cases, and expand only after both profiles receive evidence-backed dispositions.
Sources
- White Label Design: How to Build Rebrandable Products With Flexible Design Systems: White-label products require flexible tokens, reusable components, theme switching, and accessibility checks across variations.
- The Challenges of Creating a White Label Design System: Customization depth differs by product, and brand choices can conflict with accessible product use.
- White-labeling: putting the design system in users' hands: Dynamic theme data can be paired with deliberately constrained components and schema validation.
- White-label app design: how to build a product customers can rebrand: White-label applications commonly expose customer-controlled assets and values through layered token architecture.
- White-label solutions: Semantic tokens can support multiple brand languages, while logos, imagery, layout, and component limits require separate treatment.
- Best practices for fonts: Font loading and delivery choices affect fallback rendering and should be tested rather than inferred from a font declaration.
- Ambient Sage Design Kit: The published Ambient Sage kit provides semantic light and dark roles, Plus Jakarta Sans and JetBrains Mono typography roles, a compact product scale, and implementation artifacts.