White-Label Theming
ChimerAI's theming system lets you fully customise the look and feel of your app — logos, colors, fonts, dark mode — all without touching component internals. Per-workspace theming is supported for multi-tenant SaaS.
What you get
- CSS custom properties — All colors & radii are CSS variables, overridable at runtime
- Dark mode —
next-themesintegration, system preference detection - Logo & brand — Swap logos per workspace from admin panel
- Font customisation — Google Fonts or self-hosted, set per theme
- Per-workspace themes — Each workspace can have its own theme stored in the DB
- Theme preview — Live preview without page reload
Quick setup
npx chimerai add theming
Scaffolds:
app/api/theme/route.ts ← GET/PUT workspace theme
components/ThemeProvider.tsx ← Wraps app with CSS vars
lib/theme.ts ← Theme helpers & defaults
app/theming/page.tsx ← Theme editor UI
styles/theme.css ← CSS variable definitions
Theme structure
// lib/theme.ts
export interface AppTheme {
primaryColor: string; // e.g. '#6366f1'
backgroundColor: string;
textColor: string;
borderRadius: string; // e.g. '0.5rem'
fontFamily: string; // e.g. 'Inter, sans-serif'
logoUrl?: string;
darkMode: 'light' | 'dark' | 'system';
}
export const defaultTheme: AppTheme = {
primaryColor: '#6366f1',
backgroundColor: '#ffffff',
textColor: '#111827',
borderRadius: '0.5rem',
fontFamily: 'Inter, sans-serif',
darkMode: 'system',
};
Applying themes via CSS variables
// components/ThemeProvider.tsx
'use client';
import { useEffect } from 'react';
export function ThemeProvider({ theme, children }: { theme: AppTheme; children: React.ReactNode }) {
useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--color-primary', theme.primaryColor);
root.style.setProperty('--color-bg', theme.backgroundColor);
root.style.setProperty('--color-text', theme.textColor);
root.style.setProperty('--radius', theme.borderRadius);
root.style.setProperty('--font-sans', theme.fontFamily);
}, [theme]);
return <>{children}</>;
}
Saving a workspace theme
// app/api/theme/route.ts
import { db } from '@/lib/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function PUT(req: Request) {
const session = await getServerSession(authOptions);
if (!session) return new Response('Unauthorized', { status: 401 });
const theme = await req.json();
await db.workspace.update({
where: { id: session.user!.workspaceId },
data: { theme: JSON.stringify(theme) },
});
return Response.json({ ok: true });
}
Dark mode
Dark mode is handled by next-themes. Add ThemeProvider from next-themes in your root layout:
// app/layout.tsx
import { ThemeProvider } from 'next-themes';
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}