Avoidable Mistake: Dynamic Tailwind Class Access
Tags
Tailwind is powerful, but sometimes we want to bend the rules — for example, by generating class names dynamically. While this may seem convenient, it often leads to subtle issues. Let’s look at the common mistakes, why they happen, and how to handle them properly.
The Classic Mistake
As a developer, you might be tempted to build class names on the fly:
type DummyProps = {
bgColor: 'rose' | 'orange' | 'sky'
};
const Dummy = ({ bgColor }: DummyProps) => {
return <div className={`w-32 h-32 bg-${bgColor}-500`}>Hello</div>;
};
This looks reasonable, but it won’t work. Tailwind doesn’t parse string concatenations or template interpolations. Instead, it scans your source files for full, literal class names. Since bg-${bgColor}-500
doesn’t exist as a complete string, the background color is never applied.
The Correct Approach
The solution is to make your Tailwind classes explicit:
type DummyProps = {
// Tailwind classes must be declared explicitly
bgColor: 'bg-rose-500' | 'bg-orange-500' | 'bg-sky-500';
};
const Dummy = ({ bgColor }: DummyProps) => {
return <div className={`w-32 h-32 ${bgColor}`}>Hello</div>;
};
By using the full class names, Tailwind picks them up during build time, and your styles apply correctly.
Why the Wrong Way Sometimes "Works"
You might have noticed that dynamic classes occasionally work. For example:
type DummyProps = {
bgColor: 'rose' | 'orange' | 'sky'
};
const Dummy = ({ bgColor }: DummyProps) => {
return <div>
{/* This forces Tailwind to include bg-rose-500 */}
<div className="w-32 h-32 bg-rose-500">Pinkie pie rocks</div>
<div className={`w-32 h-32 bg-${bgColor}-500`}>Hello</div>
</div>;
};
Here, bg-rose-500
is explicitly written somewhere in the code, so Tailwind includes it. But this is unreliable — if someone removes that line, the dynamic class breaks. Not exactly maintainable.
Alternative: Safelisting in Tailwind
If you must generate classes dynamically, Tailwind offers a safelist mechanism. Using the @source inline
utility, you can force specific classes (or ranges of classes) into your CSS:
/* Specific background colors */
@source inline("bg-sky-500");
@source inline("bg-rose-500");
@source inline("bg-orange-500");
/* A whole range */
@source inline("bg-sky-{50,{100..900..100},950}");
The brace expansion syntax allows you to include many variants at once, e.g. {50,{100..900..100},950}
means:
- 50
: include bg-sky-50
- 100..900..100
: include bg-sky-100
through bg-sky-900
in steps of 100
- 950
: include bg-sky-950
This approach is handy for small, well-defined sets of colors.
Alternative to the Alternative: Color Objects
But what if you need all Tailwind colors, like i did in my Tailwind gradient tool or in my Tailwind button tool ?
Safelisting every color would blow up your CSS bundle, defeating Tailwind’s efficiency, so in that case, a better approach is to declare a color map directly in your code:
// Tailwind colors ( v3.4.4 / v4.1 )
export const TW_COLORS = {
slate: {
'50': '#f8fafc',
'100': '#f1f5f9',
'200': '#e2e8f0',
'300': '#cbd5e1',
'400': '#94a3b8',
'500': '#64748b',
'600': '#475569',
'700': '#334155',
'800': '#1e293b',
'900': '#0f172a',
'950': '#020617',
},
gray: {
'50': '#f9fafb',
'100': '#f3f4f6',
'200': '#e5e7eb',
'300': '#d1d5db',
'400': '#9ca3af',
'500': '#6b7280',
'600': '#4b5563',
'700': '#374151',
'800': '#1f2937',
'900': '#111827',
'950': '#030712',
},
zinc: {
'50': '#fafafa',
'100': '#f4f4f5',
'200': '#e4e4e7',
'300': '#d4d4d8',
'400': '#a1a1aa',
'500': '#71717a',
'600': '#52525b',
'700': '#3f3f46',
'800': '#27272a',
'900': '#18181b',
'950': '#09090b',
},
neutral: {
'50': '#fafafa',
'100': '#f5f5f5',
'200': '#e5e5e5',
'300': '#d4d4d4',
'400': '#a3a3a3',
'500': '#737373',
'600': '#525252',
'700': '#404040',
'800': '#262626',
'900': '#171717',
'950': '#0a0a0a',
},
stone: {
'50': '#fafaf9',
'100': '#f5f5f4',
'200': '#e7e5e4',
'300': '#d6d3d1',
'400': '#a8a29e',
'500': '#78716c',
'600': '#57534e',
'700': '#44403c',
'800': '#292524',
'900': '#1c1917',
'950': '#0c0a09',
},
red: {
'50': '#fef2f2',
'100': '#fee2e2',
'200': '#fecaca',
'300': '#fca5a5',
'400': '#f87171',
'500': '#ef4444',
'600': '#dc2626',
'700': '#b91c1c',
'800': '#991b1b',
'900': '#7f1d1d',
'950': '#450a0a',
},
orange: {
'50': '#fff7ed',
'100': '#ffedd5',
'200': '#fed7aa',
'300': '#fdba74',
'400': '#fb923c',
'500': '#f97316',
'600': '#ea580c',
'700': '#c2410c',
'800': '#9a3412',
'900': '#7c2d12',
'950': '#431407',
},
amber: {
'50': '#fffbeb',
'100': '#fef3c7',
'200': '#fde68a',
'300': '#fcd34d',
'400': '#fbbf24',
'500': '#f59e0b',
'600': '#d97706',
'700': '#b45309',
'800': '#92400e',
'900': '#78350f',
'950': '#451a03',
},
yellow: {
'50': '#fefce8',
'100': '#fef9c3',
'200': '#fef08a',
'300': '#fde047',
'400': '#facc15',
'500': '#eab308',
'600': '#ca8a04',
'700': '#a16207',
'800': '#854d0e',
'900': '#713f12',
'950': '#422006',
},
lime: {
'50': '#f7fee7',
'100': '#ecfccb',
'200': '#d9f99d',
'300': '#bef264',
'400': '#a3e635',
'500': '#84cc16',
'600': '#65a30d',
'700': '#4d7c0f',
'800': '#3f6212',
'900': '#365314',
'950': '#1a2e05',
},
green: {
'50': '#f0fdf4',
'100': '#dcfce7',
'200': '#bbf7d0',
'300': '#86efac',
'400': '#4ade80',
'500': '#22c55e',
'600': '#16a34a',
'700': '#15803d',
'800': '#166534',
'900': '#14532d',
'950': '#052e16',
},
emerald: {
'50': '#ecfdf5',
'100': '#d1fae5',
'200': '#a7f3d0',
'300': '#6ee7b7',
'400': '#34d399',
'500': '#10b981',
'600': '#059669',
'700': '#047857',
'800': '#065f46',
'900': '#064e3b',
'950': '#022c22',
},
teal: {
'50': '#f0fdfa',
'100': '#ccfbf1',
'200': '#99f6e4',
'300': '#5eead4',
'400': '#2dd4bf',
'500': '#14b8a6',
'600': '#0d9488',
'700': '#0f766e',
'800': '#115e59',
'900': '#134e4a',
'950': '#042f2e',
},
cyan: {
'50': '#ecfeff',
'100': '#cffafe',
'200': '#a5f3fc',
'300': '#67e8f9',
'400': '#22d3ee',
'500': '#06b6d4',
'600': '#0891b2',
'700': '#0e7490',
'800': '#155e75',
'900': '#164e63',
'950': '#083344',
},
sky: {
'50': '#f0f9ff',
'100': '#e0f2fe',
'200': '#bae6fd',
'300': '#7dd3fc',
'400': '#38bdf8',
'500': '#0ea5e9',
'600': '#0284c7',
'700': '#0369a1',
'800': '#075985',
'900': '#0c4a6e',
'950': '#082f49',
},
blue: {
'50': '#eff6ff',
'100': '#dbeafe',
'200': '#bfdbfe',
'300': '#93c5fd',
'400': '#60a5fa',
'500': '#3b82f6',
'600': '#2563eb',
'700': '#1d4ed8',
'800': '#1e40af',
'900': '#1e3a8a',
'950': '#172554',
},
indigo: {
'50': '#eef2ff',
'100': '#e0e7ff',
'200': '#c7d2fe',
'300': '#a5b4fc',
'400': '#818cf8',
'500': '#6366f1',
'600': '#4f46e5',
'700': '#4338ca',
'800': '#3730a3',
'900': '#312e81',
'950': '#1e1b4b',
},
violet: {
'50': '#f5f3ff',
'100': '#ede9fe',
'200': '#ddd6fe',
'300': '#c4b5fd',
'400': '#a78bfa',
'500': '#8b5cf6',
'600': '#7c3aed',
'700': '#6d28d9',
'800': '#5b21b6',
'900': '#4c1d95',
'950': '#2e1065',
},
purple: {
'50': '#faf5ff',
'100': '#f3e8ff',
'200': '#e9d5ff',
'300': '#d8b4fe',
'400': '#c084fc',
'500': '#a855f7',
'600': '#9333ea',
'700': '#7e22ce',
'800': '#6b21a8',
'900': '#581c87',
'950': '#3b0764',
},
fuchsia: {
'50': '#fdf4ff',
'100': '#fae8ff',
'200': '#f5d0fe',
'300': '#f0abfc',
'400': '#e879f9',
'500': '#d946ef',
'600': '#c026d3',
'700': '#a21caf',
'800': '#86198f',
'900': '#701a75',
'950': '#4a044e',
},
pink: {
'50': '#fdf2f8',
'100': '#fce7f3',
'200': '#fbcfe8',
'300': '#f9a8d4',
'400': '#f472b6',
'500': '#ec4899',
'600': '#db2777',
'700': '#be185d',
'800': '#9d174d',
'900': '#831843',
'950': '#500724',
},
rose: {
'50': '#fff1f2',
'100': '#ffe4e6',
'200': '#fecdd3',
'300': '#fda4af',
'400': '#fb7185',
'500': '#f43f5e',
'600': '#e11d48',
'700': '#be123c',
'800': '#9f1239',
'900': '#881337',
'950': '#4c0519',
},
} as const;
This way, you can programmatically build gradients, borders, and text colors without relying on safelists or risking missing styles.
In short
Dynamic class generation in Tailwind is a common pitfall. While it might seem to work sometimes, it’s unreliable and often leads to broken styles.
The best practice is to keep Tailwind classes explicit whenever possible.
If needed use safelisting for a controlled set of dynamic classes.
For full flexibility define your own color object and reference it programmatically.
By understanding these trade-offs, you’ll write more maintainable, predictable Tailwind code — and avoid hours of "why isn’t this working ?" frustration.