963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
3.1 KiB
3.1 KiB
Tailwind CSS Dynamic Class Generation
Rule
Never construct Tailwind CSS class names dynamically using string concatenation or template literals. Tailwind's build process cannot detect dynamically generated class names, causing styles to be missing in production.
Why This Matters
- Tailwind uses static analysis at build time to determine which CSS classes to include
- Dynamically constructed class names (e.g.,
bg-${color}-500) are invisible to Tailwind's scanner - Classes work in development with JIT but fail silently in production builds
- This is a common source of "it works locally but not in production" bugs
Bad Code
<script setup>
const props = defineProps({
color: String, // 'red', 'blue', 'green'
size: String, // 'sm', 'md', 'lg'
});
</script>
<template>
<!-- WRONG: Tailwind cannot detect these classes -->
<div :class="`bg-${color}-500 text-${size}`">Content</div>
<!-- WRONG: String concatenation -->
<div :class="'p-' + padding">Content</div>
<!-- WRONG: Template literal in array -->
<div :class="[`gap-x-${spacing}`]">Content</div>
</template>
Good Code
<script setup>
const props = defineProps({
color: String,
size: String,
});
// Use a mapping object with complete class names
const colorClasses = {
red: 'bg-red-500',
blue: 'bg-blue-500',
green: 'bg-green-500',
};
const sizeClasses = {
sm: 'text-sm p-2',
md: 'text-base p-4',
lg: 'text-lg p-6',
};
</script>
<template>
<!-- CORRECT: Full class names that Tailwind can detect -->
<div :class="[colorClasses[color], sizeClasses[size]]">Content</div>
</template>
Using Conditional Objects
<script setup>
const props = defineProps({
variant: String, // 'primary', 'secondary', 'danger'
});
</script>
<template>
<!-- CORRECT: All class names are complete strings -->
<button
:class="{
'bg-blue-500 hover:bg-blue-600': variant === 'primary',
'bg-gray-500 hover:bg-gray-600': variant === 'secondary',
'bg-red-500 hover:bg-red-600': variant === 'danger',
}"
>
Click me
</button>
</template>
Safelist for Truly Dynamic Classes
If you must use dynamic classes, add them to Tailwind's safelist:
// tailwind.config.js
module.exports = {
safelist: [
'bg-red-500',
'bg-blue-500',
'bg-green-500',
// Or use patterns (use sparingly - increases bundle size)
{
pattern: /bg-(red|blue|green)-(100|500|900)/,
},
],
};
Alternative: CSS Custom Properties
For truly dynamic values, use CSS custom properties:
<script setup>
const props = defineProps({
customColor: String, // Any hex color
});
</script>
<template>
<!-- Use CSS variable for truly dynamic values -->
<div class="dynamic-bg" :style="{ '--dynamic-color': customColor }">Content</div>
</template>
<style>
.dynamic-bg {
background-color: var(--dynamic-color);
}
</style>