AZMARA PLATFORM
Recipes

Give each tenant its own theme

Apply a brand colour from data to one part of a page, with text colours that stay readable.

View as Markdown for coding assistants, or see llms.txt.

Compute a theme from the tenant's settings and put its variables on the element that should be themed. Everything inside follows, and nothing outside changes.

Preview theme
Preview density

Kauri Timber

New
<div className="flex max-w-md flex-col gap-4">
  <ToggleGroup
    aria-label="Tenant"
    variant="outline"
    value={[tenant]}
    onValueChange={(value) => {
      const chosen = (value as string[])[0] as TenantId | undefined;
      if (chosen) setTenant(chosen);
    }}
  >
    {(Object.keys(TENANTS) as TenantId[]).map((id) => (
      <ToggleGroupItem key={id} value={id}>
        {TENANTS[id].label}
      </ToggleGroupItem>
    ))}
  </ToggleGroup>

  <div style={theme.cssVariables}>
    <Card>
      <CardHeader>
        <CardTitle as="h2">{TENANTS[tenant].label}</CardTitle>
      </CardHeader>
      <CardContent className="flex items-center gap-3">
        <Button>Place order</Button>
        <Badge>New</Badge>
      </CardContent>
    </Card>
  </div>
</div>
Open as text

The pattern

import { createTheme } from "@azmr/ui/theme";

const kauri = createTheme({ primary: "#2E7D32" }).cssVariables["--primary"];
const harbour = createTheme({ primary: "#1E88E5" }).cssVariables["--primary"];
const differs = kauri !== harbour;
// => true

Then apply the variables:

<div style={createTheme({ primary: tenant.primary }).cssVariables}>…</div>
  • createTheme does not return your colour as given. It adjusts it until the text on it, and the focus ring, meet their contrast targets in light and dark mode, so a tenant who picks a hard-to-read brand colour still gets a readable interface. The demo's third tenant is a bright yellow and gets dark text on its buttons. See what it guarantees.
  • It throws an error that names the option when a value is not a valid colour, so validate what a tenant types before it reaches the page, and show them the message.
  • Put the variables on the element, not only on <html>: a scope that sets its own data-theme re-declares every colour, so variables set outside it do not reach inside. See Apply the theme.
  • For the whole app, apply the theme to <html>, and do it on the server so the first paint is already right.