'Dynamically build classnames in TailwindCss
I am currently building a component library for my next project with TailwindCss, I just ran into a small issue when working on the Button component.
I'm passing in a prop like 'primary'
or 'secondary'
that matches a color I've specified in the tailwind.config.js
then I want to assign that to the button component using Template literals
like so: bg-${color}-500
<button
className={`
w-40 rounded-lg p-3 m-2 font-bold transition-all duration-100 border-2 active:scale-[0.98]
bg-${color}-500 `}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</button>
The class name comes through in the browser just fine, it shows bg-primary-500
in the DOM, but not in the applied styles tab.
The theming is configured like so:
theme: {
extend: {
colors: {
primary: {
500: '#B76B3F',
},
secondary: {
500: '#344055',
},
},
},
},
But it doesn't apply any styling. if I just add bg-primary-500
manually it works fine.
I'm honestly just wondering if this is because of the JIT compiler not picking dynamic classnames up or if I'm doing something wrong (or this is just NOT the way to work with tailWind).
Any help is welcome, thanks in advance!
Solution 1:[1]
So after finding out that this way of working is not recommended and that JIT doesn't support it (Thanks to the generous commenters). I have changed the approach to a more 'config' based approach.
Basically I define a const with the basic configuration for the different props and apply those to the component. It's a bit more maintenance work but it does the job.
Here is the example of a config. (Currently without typing) and up for some better refactoring but you'll get the idea.
const buttonConfig = {
// Colors
primary: {
bgColor: 'bg-primary-500',
color: 'text-white',
outline:
'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
},
secondary: {
bgColor: 'bg-secondary-500',
color: 'text-white',
outline:
'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
},
// Sizes
small: 'px-3 py-2',
medium: 'px-4 py-2',
large: 'px-5 py-2',
};
Then I just apply the styling like so:
<motion.button
whileTap={{ scale: 0.98 }}
className={`
rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
${buttonConfig[size]}
${outlined && buttonConfig[color].outline}
${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</motion.button>
Solution 2:[2]
this way of writing Tailwind CSS classes is not recommended. Even JIT mode doesn't support it, to quote Tailwind CSS docs: "Tailwind doesn’t include any sort of client-side runtime, so class names need to be statically extractable at build-time, and can’t depend on any sort of arbitrary dynamic values that change on the client"
Solution 3:[3]
As someone already said in this thread TailWindCSS 3 does not support dynamic class names (see https://tailwindcss.com/docs/content-configuration#dynamic-class-names).
I used the "safelist" feature to exclude some class names from purging. In the tailwind.config.js you can use pure JS to develop something like this: (Its a little hacky, I agree)
const colors = require('./node_modules/tailwindcss/colors');
const colorSaveList = [];
const extendeColors = {};
for (const key in colors) {
extendeColors[key] = colors[key];
[100, 200, 300, 400, 500, 600, 700, 800, 900].forEach(colorValue => {
colorSaveList.push(`text-${key}-${colorValue}`);
colorSaveList.push(`bg-${key}-${colorValue}`);
});
}
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
safelist: colorSaveList,
theme: {
extend: {
colors: extendeColors
}
},
plugins: [
require('tailwind-scrollbar'),
]
}
Solution 4:[4]
For tailwind JIT
mode or v3 that uses JIT
, you have to ensure that the file where you export the object styles is included in the content option in tailwind.config.js
, e.g.
content: ["./src/styles/**/*.{html,js}"],
Solution 5:[5]
In v3 as Blessing said you can change the content array to support that.
I had this
const PokemonTypeMap = {
ghost: {
classes: "bg-purple-900 text-white",
text: "fantasma",
},
normal: {
classes: "bg-gray-500 text-white",
text: "normal",
},
dark: {
classes: "bg-black text-white",
text: "siniestro",
},
psychic: {
classes: "bg-[#fc46aa] text-white",
text: "psíquico",
},
};
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span
className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans'] italic"}
>
{pokemonType.text}
</span>
);
}
export default PokemonType;
something similar to your approach, then I moved the array to a JSON file, it thought was working fine, but was browser caché... so following Blessing's response, you can add .json like this
content: ["./src/**/*.{js,jsx,ts,tsx,json}"],
Finally I have this code, it's better in my view.
import PokemonTypeMap from "./pokemonTypeMap.json";
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans']"}>
{pokemonType.text}
</span>
);
}
export default PokemonType;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Wesley Janse |
Solution 2 | rokob |
Solution 3 | A. Mrozek |
Solution 4 | |
Solution 5 | Dharman |