If you are building a trading platform for multiple clients - brokers, exchanges, or liquidity providers - you will eventually need to white-label it. Each client wants their logo, their colours, and ideally their custom domain. The technical challenge is supporting this without maintaining separate codebases.
What white-labelling actually requires
At minimum, clients expect to customise:
- Logo and favicon - Their brand identity in the header and browser tab
- Colour scheme - Primary brand colour applied throughout the interface
- Typography - Some clients have brand fonts
- Domain - Trading on
trade.clientbrand.com, not your domain - Email templates - Notifications that come from their brand
More demanding clients also want:
- Custom landing pages - Different marketing content per client
- Feature toggles - Different modules enabled per client
- Custom terminology - "Wallet" vs "Account", "Spot" vs "Cash"
The architecture
Tenant configuration
Define a tenant configuration type that captures everything that varies between clients:
interface TenantConfig { id: string; name: string; domain: string; theme: { primaryColor: string; primaryForeground: string; logoUrl: string; faviconUrl: string; fontFamily?: string; }; features: { spot: boolean; futures: boolean; margin: boolean; staking: boolean; }; terminology: Record<string, string>; }
Load this configuration at application startup based on the domain or a subdomain identifier.
CSS custom properties as the theming layer
CSS custom properties are the correct abstraction for white-labelling. They cascade through the DOM, can be set at runtime, and require zero JavaScript to apply:
function applyTenantTheme(config: TenantConfig) { const root = document.documentElement; root.style.setProperty("--color-primary", config.theme.primaryColor); root.style.setProperty("--color-primary-foreground", config.theme.primaryForeground); if (config.theme.fontFamily) { root.style.setProperty("--font-sans", config.theme.fontFamily); } }
Your components reference these variables through Tailwind or direct CSS. When the variables change, the entire UI updates without re-rendering a single React component.
Isolating brand assets
Store tenant assets (logos, favicons, custom images) in a structured directory or CDN path:
/tenants /client-a logo.svg favicon.ico og-image.png /client-b logo.svg favicon.ico og-image.png
Reference these through the tenant configuration rather than hardcoding paths.
Feature toggling
Not every client gets every feature. Use the tenant configuration to conditionally render modules:
function TradingLayout({ config }: { config: TenantConfig }) { return ( <Workspace> <ChartPanel /> <OrderBookPanel /> {config.features.futures && <FuturesPanel />} {config.features.margin && <MarginPanel />} {config.features.staking && <StakingPanel />} </Workspace> ); }
For more granular control, use a feature flag service that can be updated without deployments.
Custom terminology
Some clients call the same concept by different names. A translation layer keeps this out of your component logic:
function useTerm(key: string): string { const config = useTenantConfig(); return config.terminology[key] ?? key; } // In a component const walletLabel = useTerm("Account"); // Returns "Wallet" for Client A
Keep the default terminology in the components and override only where the client differs.
Multi-domain deployment
Each client wants their own domain. In Next.js, handle this with middleware:
// middleware.ts export function middleware(request: NextRequest) { const hostname = request.headers.get("host") ?? ""; const tenant = getTenantByDomain(hostname); if (tenant) { request.headers.set("x-tenant-id", tenant.id); } return NextResponse.next({ request }); }
The tenant ID propagates through the request, and server components can load the correct configuration.
Testing white-label configurations
Every tenant configuration is a potential source of bugs. Test each one:
- Visual regression tests - Capture screenshots of key pages with each tenant's theme applied. Compare against baselines.
- Feature matrix tests - Verify that enabled/disabled features render correctly per tenant.
- Theme contrast checks - Ensure that each client's primary colour meets contrast requirements against your surface colours.
Automate these in CI with a matrix build that iterates over tenant configurations.
Common mistakes
- Forking the codebase per client - This scales to about 3 clients before becoming unmaintainable. Use configuration, not branches.
- Hardcoding colours in components - Every colour reference must go through the token system. A single hardcoded
#3b82f6will look wrong in every non-default theme. - Client-specific logic in core components - If you find yourself writing
if (tenant === "clientA"), extract the varying behaviour into the configuration. - Forgetting email templates - Users receive emails from the platform. These need to match the client's branding too.
Starting from the right foundation
White-labelling is dramatically easier when the application is built with theming from day one. Retrofitting CSS custom properties onto a codebase with hardcoded colours and inline styles is painful and error-prone.
If you know your platform will serve multiple clients, invest in the token-based theming system early. The marginal cost of making a component theme-aware during development is far lower than refactoring it later.