Tailwind CSS is one of the most popular and widely used CSS libraries. It leverages utility classes to build responsive designs without having to write extensive CSS. It does not use pre-styled components like Bootstrap or Material UI.
In this article, we are going to focus on how to build custom variants for a multi-tenant frontend application.
This article is for those with a basic understanding of CSS and Tailwind CSS. You don't need to be a frontend expert to follow along.
Concept of Variants in Tailwind CSS
Tailwind CSS variants are sets of predefined classes that allow developers to apply certain styles conditionally. By using variants, developers can create different styles for a component or element according to different states. For instance, when a button is disabled, the disabled:
variant can be used followed by the utility class to apply a specific style.
In Tailwind, there are many predefined variants that we can classify into five categories:
Responsive variants: These variants are used to apply certain styles according to screen dimensions. In this category, we have
sm:
,md:
,max-lg:
, etc.Pseudo-class variants: These variants are used to apply styles based on specific state conditions. In this category, we have
hover:
,enabled:
,checked:
, etc.Group variant: The group variant is used to style elements according to the state of the group to which the element belongs. It is written as
group:
.Peer variant: The peer variant is used to style an element based on the state of a peer element. In this case, the component does not have to belong to a group, it is styled according to the state of a parallel component. It is written as
peer:
.Custom variants: Custom variants are implemented with the
addVariant
API.
Custom Variants
A custom variant can be added to Tailwind CSS as a plugin using the addVariant
API. This API allows developers to extend Tailwind CSS by implementing customized variants that can be used like the built-in ones. One well-known custom variant, now adopted as a default in Tailwind CSS, is dark:
, which is used to apply styles when the application is in dark mode.
1. Creating a Simple Custom Variant
The code below shows how a custom variant can be implemented as a plugin in tailwind.config.js
:
const plugin = require('tailwindcss/plugin');
module.exports = {
plugins: [
plugin(function({ addVariant }) {
addVariant('custom', '&:optional');
})
]
};
We use the plugin
function from tailwindcss/plugin
, which gives us access to the addVariant
method. The addVariant
method takes two arguments: the first is the name of the variant, and the second is its definition, which can be a string or a function for adding more support with other predefined variants like group:
and peer:
.
2. Custom Variant with Modifiers
For this step, we'll understand how modifiers can create a scope for all descendant class utilities. The dark:
modifier for dark mode is implemented similarly.
Before diving deeper, let's understand how the dark:
modifier works in Tailwind CSS.
For applying dark mode styles to our components or elements, we can use these approaches:
<div className="text-white dark:text-black"></div>
In this first approach, we explicitly add the modifier to the element to change its styles in dark mode, which is straightforward. However, an application is not just a single div
; it often has hundreds of components with different colors in dark mode, which leads us to the second approach.
<html className="dark">
<div className="text-foreground"></div>
</html>
In this second approach, the variant is applied only to the html
tag, considered the top-level tag of our application or page. We use a custom color name, which changes the color value according to the variant placed on the top-level html
tag.
To achieve this pattern, we don't only need a custom variant but also a modifier to modify all the class utilities scoped under the variant applied to the top-level tag.
Here's how to add a plugin with a modifier:
plugin(function ({ addVariant, e }) {
addVariant("dark", ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.dark .${e(`dark${separator}${className}`)}`;
});
});
});
In this code, the callback function from the plugin
function also destructures e
, a function to escape strings. The callback returns a string that defines how the variant should modify the selector:
-
.dark
: Targets any element with the class "dark." - A space: Creates a descendant combinator, which is used to select elements that are descendants of another element.
-
.
: Starts a new class selector. -
e(dark${separator}${className})
: Generates the variant class name.
The generated CSS will look like this:
.dark .dark\:text-foreground {
/* styles here */
}
Now that we understand how to create simple custom variants and custom variants with modifiers, we will use these principles for the multi-tenant UI design problem.
Multi-tenant UI Design with Tailwind CSS
For this article, we'll focus on the frontend of a multi-tenant architecture. A multi-tenant frontend application serves multiple customers or groups (tenants) with a single application without requiring separate instances or code changes for each tenant.
1. Problem and Requirements
Let's consider two tenants, TenantA and TenantB, both with different branding and types of data, but they share the same frontend application. The branding color of TenantA is green, while TenantB's is yellow. Both tenants are restaurants and use the same frontend application but want it to reflect their branding.
2. Solution
To meet the requirements of TenantA and TenantB, we will follow a few steps:
1. Defining Colors Globally
One key to a successful multi-tenant frontend UI design is a good base structure for defining color names. Since both tenants will share the same components, with only different styling, the color names should be the same, with different values. In the CSS entry point, which could be main.css
, global.css
, or index.css
depending on the structure, we define a color name called branding-color
at the root level as a CSS variable.
:root {
.tenantA {
--branding-color: #008000;
}
.tenantB {
--branding-color: #FFFF00;
}
}
2. Setting Colors in Tailwind CSS Config
After defining the branding colors globally, Tailwind CSS is not yet aware of our colors, so we need to set them up in the tailwind.config.js
file under the extended colors
object.
module.exports = {
extend: {
colors: {
"branding-color": "var(--branding-color)"
}
}
};
3. Creating Tenant Plugins
The colors are configured in Tailwind, but they cannot be used for the two tenants without creating their respective plugins with modifiers.
const plugin = require('tailwindcss/plugin');
module.exports = {
extend: {
colors: {
"branding-color": "var(--branding-color)"
}
},
plugins: [
plugin(function({ addVariant, e }) {
addVariant("tenantA", ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.tenantA .${e(`tenantA${separator}${className}`)}`;
});
});
addVariant("tenantB", ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.tenantB .${e(`tenantB${separator}${className}`)}`;
});
});
})
]
};
Now, we have configured the tenant plugins, and we can easily use them to apply different colors for different tenants:
<html className="tenantA">
<div className="text-branding-color"></div>
</html>
<html className="tenantB">
<div className="text-branding-color"></div>
</html>
The only change is in the class on the top-level tag, which is enough for the entire application to have a different look.
Conclusion
As multi-tenant frontend applications are usually large and complex, Tailwind CSS variants can be used to have smooth transitions between colors and control the visibility of different components, similar to normal default variants.
Thanks for reading.
Learn More:
- Tailwind CSS plugins: Tailwind CSS Documentation
- Shadcn dark mode setup: Shadcn Dark Mode Guide
- Tailwind CSS variants implementation: Tailwind GitHub Repository
Top comments (2)
Nice one š
So Insightful