Files
agent-skills/skills/vue-best-practices/reference/tailwind-dynamic-class-generation.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

145 lines
3.1 KiB
Markdown

# 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
```vue
<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
```vue
<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
```vue
<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:
```javascript
// 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:
```vue
<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>
```
## References
- [Tailwind CSS Dynamic Class Names](https://tailwindcss.com/docs/content-configuration#dynamic-class-names)
- [Tailwind Safelist](https://tailwindcss.com/docs/content-configuration#safelisting-classes)