Have you ever had to live with two Tailwind themes that set different values and/or tokens for the same Tailwind property?
Suppose you're part of a team responsible for maintaining a design system and you need to redo that design system. However, this design system is already being used in an application. It has a package that defines the Tailwind theme, which is used both in the design system itself and in the application, with customized tokens and values. During the migration, you need to update and change some of the values and tokens in the new Tailwind theme. How could you do this in the new design system so that you don't have to change all the components in the app to new DS and all the pages that is using the old theme at once?
At first, you could think about using the concept of Tailwind Preset in the new design system, defining a prefix to differentiate the old class names from the new ones. That way, in the application, you could use both theme packages and, consequently, make a gradual change to the components.
Your tailwind.config.js file would look something like this:
import oldTheme from "old-ds";
import newPreset from "new-ds";
/** @type {import('tailwindcss').Config} */
export default {
presets: [newPreset]
theme: oldTheme,
};
or
import oldPreset from "old-ds";
import newPreset from "new-ds";
/** @type {import('tailwindcss').Config} */
export default {
presets: [oldPreset, newPreset]
};****
Where newPreset would look like:
export default {
theme: /** your theme */
plugins: /** your plugins */
prefix: 'new-'
}
However, as soon as you migrate the first component to the new DS, you'll realize that this won't work, unless you export the stylesheets of your new components and import them into your application. This, however, will prevent you from using the new theme and tokens directly in your application pages until you have finished migrating the components.
This is due to the way Tailwind performs the merge of presets and configurations. According to the documentation itself:
When adding multiple presets, it’s important to note that if they overlap in any way, they are resolved the same way your own customizations are resolved against a preset, and the last configuration wins.
In other words, if both your old theme/preset and the new one set a value for borderRadius, even if one of the two versions is prefixed, in your application Tailwind will use the last configuration loaded.
So how could we solve this problem?
The documentation suggests that you set your themes inside the extend
property instead of simply overwriting the configuration. This works up to a point, but if both your presets define an sm
key for the borderRadius, and in one the value is 4px
and in the other it is 8px
, only one of these two values will be used.
The solution I found is based on using Tailwind Plugins. Using the addUtilities
function, you can use the theme of your Tailwind configuration to manually generate the prefixed classes, creating a plugin with your entire theme instead of a preset. This allows you to use the components and class names of both design systems in your application simultaneously, without having to perform the entire migration at once.
So, after defining your theme's tokens and values, you can turn them into Tailwind utilities. In my case, this was done using the createUtilities
function. The disadvantage of this approach is the overhead required to map all the modifiers that a theme key can change. For example, the color key applies to bg-{color}
, text-{color}
and border-{color}
.
Open two terminals. In one of them, type the command to compile Tailwind in watch mode:
pnpm build:twcss
In the other, start the application:
pnpm dev