The setup: a DTC client with two brands that shared nothing in the consumer-facing experience but had overlapping engineering requirements. Both on Shopify. Both running similar email flows. Both built by the same internal design and engineering team of four. The brief was to build each brand's front-end independently while the underlying infrastructure could reasonably be shared.
What I built over four months was a shared design token base plus two brand-specific layers. It mostly worked. Three things broke and had to be fixed. Here is the postmortem.
The architecture
The tokens file has three layers. Architectural tokens at the base (spacing, radius, motion, shadow). Brand-specific tokens in a per-brand layer (color, type, voice). Component-level tokens at the top (button hover state, PDP hero spacing, email footer padding) that may or may not override the brand layer.
/* Shared architectural layer (one file, both brands) */
--space-1: 4px;
--space-2: 8px;
--space-3: 16px;
--radius-sm: 4px;
--radius-md: 10px;
--motion-fast: 140ms;
--motion-slow: 320ms;
--t-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
/* Brand A overrides (separate file) */
:root[data-brand="A"] {
--signal: #fd109a;
--ink: #0a0a0a;
--paper: #fafafa;
--font-display: "DM Serif Display";
--font-body: "DM Sans";
}
/* Brand B overrides (separate file) */
:root[data-brand="B"] {
--signal: #00c48c;
--ink: #111a1f;
--paper: #f6f0e4;
--font-display: "Fraunces";
--font-body: "Inter";
}
Each brand's Shopify theme sets data-brand="A" or data-brand="B" on the root element. All components reference the semantic tokens (--signal, --ink, --paper). The same component renders differently per brand without per-component branching.
The shared architectural tokens are about 18-22 lines. Each brand's override layer is about 4-6 tokens. That ratio (roughly 80 percent shared, 20 percent brand-specific) is what made the architecture economically worth it.
What belongs in the shared base
The rule I use: anything that is a structural decision belongs in the shared base. Anything that is an expressive decision belongs in the brand layer.
Structural decisions: how far apart are things (spacing), how rounded are corners (radius), how fast does the UI move (motion), what is the base grid rhythm (spacing scale). These are architectural choices that apply to any DTC brand, and most DTC brands would benefit from having the same answers to them. A 4px spacing base is appropriate for almost any brand.
Expressive decisions: what color is the brand, what fonts carry the headlines and body, what feels distinctive about the voice, what is the brand's photographic style. These are per-brand choices and should stay per-brand.
What broke: drift 1, the typography scale
The first break was that both brands initially used the shared type scale with only a font-family override. "Brand A uses DM Serif for display; Brand B uses Fraunces for display. Everything else is shared."
Three weeks in, the Brand B team said "Fraunces at 72px reads bigger than DM Serif at 72px; our hero is overwhelming." True. Different faces render at different visual weights, and sharing the numeric scale across different families produces uneven results.
The fix was to parameterize the top two steps of the type scale per brand. Brand A kept --t-display: 72px. Brand B got --t-display: 64px. The other four steps stayed shared because they were dense enough (at 22px, 16px, 13px) that the visual difference across families was negligible.
Lesson: when two brands use different display faces, the display-scale top step almost always needs per-brand tuning. Plan for this from day one instead of assuming the numeric scale can be universal.
What broke: drift 2, the motion curve
The shared motion tokens (--motion-fast: 140ms; --t-ease: cubic-bezier(.2,.8,.2,1)) worked for Brand A but felt wrong on Brand B.
Brand A's personality is aggressive and graphic; fast motion with a snappy ease. Brand B's personality is softer and editorial; the same motion felt impatient on that brand.
The fix was per-brand motion values for the "brand personality" motion tokens, while keeping the architectural motion tokens (--t-ease-out, --motion-instant) shared.
/* Shared */
--motion-instant: 80ms;
--t-ease-out: cubic-bezier(0, 0, 0.2, 1);
/* Brand A */
:root[data-brand="A"] {
--motion-default: 140ms;
--t-ease-default: cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Brand B */
:root[data-brand="B"] {
--motion-default: 240ms;
--t-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
}
Lesson: motion is partly architectural and partly expressive. The fast functional motions (tooltip fade, instant tap feedback) are architectural. The slow expressive motions (page transitions, hero reveals) are per-brand.
What broke: drift 3, the spacing scale on mobile
The shared spacing scale worked at desktop. On mobile, Brand A felt "tight and energetic" while Brand B felt "cramped and stressful" at the same spacing values. Same math, different perceived result.
The root cause was that Brand B used slightly larger body typography and more decorative detail per-page, which needed more breathing room to read as intended. Brand A's graphic minimalism could live in tighter spacing comfortably.
The fix I shipped: a per-brand spacing multiplier on mobile breakpoints.
/* Shared base */
--space-2: 8px;
--space-3: 16px;
--space-4: 24px;
/* Brand B gets a 1.25x multiplier on mobile */
@media (max-width: 640px) {
:root[data-brand="B"] {
--space-2: 10px;
--space-3: 20px;
--space-4: 30px;
}
}
This was the fix I was most reluctant to ship because it violated the "shared architecture, brand expression" principle. But the user-facing result was right. Lesson: rules are heuristics. When the shipped experience says the rule is wrong, the rule loses.
What held up
Radius, shadow, focus states, and the component layer all held up beautifully. Each of those was genuinely architectural and did not need per-brand tuning. The shared-architecture hypothesis worked where the rule was honest.
What also held up was the development workflow: one codebase, one CI pipeline, one deploy process for two brand sites. The savings there were dramatic. Engineering could ship features (a new PDP section, a cart drawer update) to both brands in one pull request. The second brand launched about six weeks faster than it would have if it had been a separate codebase, and the ongoing maintenance savings compound every quarter.
“Sharing architecture between brands saves calendar time; sharing expression breaks customer experience. The distinction is load-bearing.
”
When this pattern is worth building
Three conditions, all true.
Condition one: both brands run on the same tech stack. Two Shopify brands can share; a Shopify brand and a Next.js brand cannot. The infrastructure has to be genuinely compatible for the token-sharing to pay off.
Condition two: the same team owns both builds. If Brand A has a team in Austin and Brand B has a team in Berlin, the shared codebase becomes a political problem. Teams prefer their own codebases. The pattern only works when one team runs both.
Condition three: both brands are below $10M and above $500K. Below $500K, neither brand is big enough to justify the architectural investment. Above $10M, each brand has enough budget to justify its own full engineering team and the shared base becomes unnecessary overhead.
For two brands that meet all three conditions, the shared-token pattern saves roughly 30-40 percent of engineering and design calendar time over the first 12-18 months. That is substantial.
When not to use this pattern
If the two brands are going to be sold to different acquirers, shared infrastructure complicates the separation. Every acquisition deal I have watched where the target had shared infrastructure took 3-6 months longer to close because the code had to be extracted first. If M&A is on the table, keep the codebases separate from day one.
If the two brands have fundamentally different tech requirements (one needs a headless storefront, the other needs a Shopify Plus checkout extension), forcing them into a shared base creates a worst-of-both-worlds codebase. Keep them separate.
If the engineering team cannot keep the brand-specific and shared tokens disciplined (the team keeps writing brand-specific rules into the shared base because it is easier), the shared base silently erodes until it is a hairball that serves neither brand. The team discipline is a real prerequisite, not a nice-to-have.
Frequently asked questions
How do you decide whether a new token goes in the shared base or a brand layer?
The test: if you would make the same decision for any DTC brand, it is architectural. If the answer depends on which brand, it is expressive. Spacing scales, radius, shadow, and motion easings tend to be architectural. Color, type, and brand-voice tokens are always expressive.
What if a brand outgrows the shared base?
Extract it. The extraction cost at 18 months is roughly 2-4 weeks of engineering work. If the brand is past $10M and diverging meaningfully, that cost is cheap and the separation unlocks independent iteration. Do not keep a brand on the shared base out of sunk-cost attachment.
Can this pattern support more than two brands?
Up to about four, beyond which the abstraction leaks. Each new brand adds roughly 4-6 brand-specific tokens, which is cheap, but the shared base starts to accumulate exceptions ("this rule applies to three of the four brands") that undermine the architectural value. Past four, you have a design system tool problem, not a token file problem.
How do you handle component-level overrides between brands?
The same way: the component references semantic tokens, and each brand can override the semantic token in its root. If Brand A's button is 8px radius and Brand B's button is 4px radius, both buttons render with var(--btn-radius) and each brand's root file sets that token. The component does not branch per brand.
Does this require a design system tool like Style Dictionary?
Not for two brands. A hand-maintained CSS file with sections per brand works cleanly. I have used Style Dictionary on larger systems (4+ brands, 3+ platforms) where the transform layer earns its keep. For the two-brand case, it is overhead without proportionate benefit.
Sources and specifics
- Pattern from a two-brand DTC engagement in 2024-2025. Specific drift stories (typography, motion, mobile spacing) are from that engagement's postmortem notes.
- The 30-40 percent calendar-time saving is the measured delta between projected single-codebase development time and actual shared-base shipping time across the first 12 months of the engagement.
- The 80/20 shared-vs-brand token ratio is the steady-state observed across both brands once the system stabilized after the three fixes described.
- The "avoid shared infrastructure if M&A is on the table" observation is from two DTC acquisitions I have observed from the outside; the shared infrastructure added 3-6 months to each deal's integration timeline.
- See also: the brand architecture hub, design tokens that survive a rebrand, and house of brands vs branded house.
- Full methodology is part of the Operator's Stack.
