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.
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>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;
// => trueThen apply the variables:
<div style={createTheme({ primary: tenant.primary }).cssVariables}>…</div>createThemedoes 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 owndata-themere-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.