← Back to blog

How to White-Label a Trading Application: A Developer's Guide

Architecture decisions that make a trading UI themeable by multiple clients — CSS custom properties, runtime theme switching, and isolating client branding from core components.


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:

  1. Visual regression tests — Capture screenshots of key pages with each tenant's theme applied. Compare against baselines.
  2. Feature matrix tests — Verify that enabled/disabled features render correctly per tenant.
  3. 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 #3b82f6 will 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.

Kickstart Your Trading Application

Hedge UI is a React starter kit with production-ready trading components, real-time data handling, and customisable layouts — so you can ship faster.

Get Hedge UI