Open rpfelgueiras opened 11 months ago
I currently have the same requirement and had no luck with radix-theme yet - see #379
@chrishoermann I was searching for a solution and I will probably go in this direction https://github.com/tailwindlabs/tailwindcss/discussions/11964
In build time generate the right theme configurations per tenant and place the output in the right server/folder.
I have theming implemented in my app. It's a multi-faceted approach that involves a combination of dynamically importing css and objects with tailwind classes.
Essentially all you need to do is get your subdomain or domain from next/headers at the root layout. Then use that to dynamically load your required theming info.
export default async function RootLayout(props: LayoutProps) {
const headersList = headers();
const host = headersList.get('host');
const site = await getSiteData(host);
...
In my case getSiteData() looks like this - I have my hostname parsing function here
// Extract subdomain helper
function extractSubdomain(domain: string) {
if (domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`)) {
return domain.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, '');
} else if (domain.endsWith('.localhost:3000')) {
return domain.replace('.localhost:3000', '');
}
return null;
}
export async function getSiteData(domain: string | null) {
if (!domain) {
return console.log("No domain provided");;
}
const subdomain = extractSubdomain(domain);
return await unstable_cache(
async () => {
return prisma.site.findUnique({
where: subdomain ? { subdomain: subdomain } : { customDomain: domain },
include: {
user: true,
activeTheme: true,
},
});
},
[`${domain}-metadata`],
{
revalidate: 900,
tags: [`${domain}-metadata`],
},
)();
}
Colour and font tokens are set by CSS variables and called for at the root with a data attribute. Those CSS variables are also set into my tailwind.config, just like @rpfelgueiras has shown in the link he referenced.
Set your data attributes on your tag. This is where you want font definitions especially
<html
{ "data-color-theme": `theme-${themeName}-color` }
{ "data-font-theme": `theme-${themeName}-font` }
>
Define CSS variables
@layer base {
[data-color-theme='theme-themeName-color'] {
--background: 0 0% 99%;
--foreground: 45 25% 5%;
--primary: 45 25% 5%;
--primary-foreground: 45 25% 98%;
...
Then, tailwind classes are put into a theme-{themeName}.tsx file and applied in the site template code after dynamically importing the tailwind classes
Theme file
const themeName: Theme = {
name: "The Theme Name",
global: {
text: "text-primary font-light",
buttonRadius: "rounded-full"
},
...
Dynamic import. This depends on naming your files with the same values you're using in your database to identify a site's theme.
const themeData = await import(`@/styles/themes/theme-${themeName}/theme-${themeName}`);`
Applied in components
<div className={cn(theme.profile.container)}>
<p className={cn(theme.profile.name)}>{data?.displayName}</p>
<div className={cn(theme.profile.infoContainer)}>
...
In my case, I'm not swapping out large chunks of JSX (yet). I'm just changing style. But there's no stopping you from having entire components conditionally render this way. It's a bit tedious to set up initially. But once the architecture is done, creating new themes is a bit less work.
I have a ServerSideThemeWrapper
wrapping everything in the [domain] route, which has access to the relevant Site, from which I pass the theme props.
Using shadcn/ui:
export function ServersideThemeWrapper({
children,
className,
color,
radius,
}: ServersideThemeWrapperProps) {
return (
<div
className={cn(`theme-${color}`, "w-full", className)}
style={
{
"--radius": `${radius ? radius : 0.5}rem`,
} as React.CSSProperties
}
>
{children}
</div>
);
}
I have a
ServerSideThemeWrapper
wrapping everything in the [domain] route, which has access to the relevant Site, from which I pass the theme props.Using shadcn/ui:
export function ServersideThemeWrapper({ children, className, color, radius, }: ServersideThemeWrapperProps) { return ( <div className={cn(`theme-${color}`, "w-full", className)} style={ { "--radius": `${radius ? radius : 0.5}rem`, } as React.CSSProperties } > {children} </div> ); }
Mind sharing repo projects so far?
I see that Hashnode is loading a different theme (or UI settings) based on the tenant. How do I do the same?