Hi there! Not that-skilled-yet Javascript developer here, using React and Next, more specifically this template
When it comes to declare component class names, I'm using the following utility function, that combines tailwind-merge
and clsx
, as suggested here:
// utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
To make things easier, improve DRY and even readability, I wish to be able to dynamically insert tailwind modifiers (e.g dark:
, hover:
, md:
...) while declaring these class names, like in the following:
// page.tsx
import { cn, dark, hover, md } from '@/utils'
<Component className={cn(
"text-white text-sm",
"w-full h-10",
hover("font-bold text-lime-500"),
md(
"w-1/2 h-20",
"text-xl"
)
)}/>
To achieve so, I implemented some other utilities functions:
// utils.ts
function apply_modifier(modifier: string, ...inputs: string[]) {
return inputs.map((input) => `${modifier}:${input}`).join(" ")
}
function create_specialist_apply_modifier_function(modifier: string) {
return (...inputs: string[]) => apply_modifier(modifier, ...inputs)
}
const dark = create_specialist_apply_modifier_function("dark")
const hover = create_specialist_apply_modifier_function("hover")
const md = create_specialist_apply_modifier_function("md")
...
I tested it out and I got the string I was expecting every time, however, the results aren't being applied to the component at all, and I couldn't understand why
Even the following won't work:
<Component className={clsx(
["text-2xl", "text-white"].map((c) => `dark:${c}`).join(" ")
)}/>
I appreciate any thoughts on understand the problem and also into figure an alternative solution
Obrigado!
As per the documentation:
The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS:
Don’t construct class names dynamically
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
In the example above, the strings
text-red-600
andtext-green-600
do not exist, so Tailwind will not generate those classes.Instead, make sure any class names you’re using exist in full:
Always use complete class names
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
The most straight-forward solution would be to use full class names:
<Component className={cn(
"text-white text-sm",
"w-full h-10",
"hover:font-bold hover:text-lime-500",
"md:w-1/2 md:h-20",
"md:text-xl"
)}/>
Alternatively, you could safelist classes you'd produce, but this would get unmaintainable fast and you'd have to context-switch a lot between the templates and the Tailwind configuration.
If you really wanted your functions to work, you could look at transforming content files just before the Tailwind class extraction, such that the transformer converts the function calls to full class names. Note, this only transforms the string content scanned by Tailwind and would not affect the operable code in the project:
<Component className={cn(
"text-white text-sm",
"w-full h-10",
hover("font-bold text-lime-500"),
md(
"w-1/2 h-20",
"text-xl"
)
)}/>
// After some trickery, perhaps something like ↓
<Component className={cn(
"text-white text-sm",
"w-full h-10",
hover("hover:font-bold hover:text-lime-500"),
md(
"md:w-1/2 md:h-20",
"md:text-xl"
)
)}/>