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.