Tailwind CSS Design System
Tailwind CSS v4 design system patterns: CSS-first configuration, design token hierarchy, CVA component variants, dark mode, and responsive grid systems.
Tags
README
tailwind
Tailwind CSS v4 design system patterns.
rules/tailwind.md is path-scoped (#1061): it carries paths: frontmatter and loads only after Claude reads a file matching one of **/*.css, **/tailwind.config.*, **/postcss.config.*, **/*.tsx, **/*.jsx, **/*.vue, **/*.svelte, **/*.astro. It costs no context at session start otherwise.
rules/frontend-css.md is path-scoped the same way, on **/*.css, **/tailwind.config.*, and **/postcss.config.*.
What It Does
Installs two rules files covering Tailwind v4 architecture and a known gotcha:
- CSS-first configuration - Use @theme in CSS instead of tailwind.config.ts
- Design token hierarchy - Primitive, semantic, and component token layers
- OKLCH color system - Perceptually uniform colors with full scales
- CVA component variants - Type-safe variant composition with class-variance-authority
- Dark mode - Class-based switching with @custom-variant, context providers, localStorage persistence
- Responsive patterns - Mobile-first, grid variants, size-* shorthand
- Native CSS animations - @keyframes in @theme, @starting-style for entry animations
- v3 to v4 migration - Reference table for common pattern changes
- cursor: pointer gotcha - Tailwind v4 preflight drops
cursor: pointeron buttons; base-style fix and where to put it
Manual Installation
# Global (all projects)
mkdir -p ~/.claude/rules
cp rules/tailwind.md ~/.claude/rules/tailwind.md
cp rules/frontend-css.md ~/.claude/rules/frontend-css.md
# Project-level
mkdir -p .claude/rules
cp rules/tailwind.md .claude/rules/tailwind.md
cp rules/frontend-css.md .claude/rules/frontend-css.md
Files
| File | Description |
|---|---|
rules/tailwind.md |
Tailwind v4 design system guide with tokens, CVA, dark mode, and migration notes |
rules/frontend-css.md |
Tailwind v4's missing cursor: pointer on buttons - base-style fix and where to put it in a project |
Will install
| Path | Action | Target | Type |
|---|---|---|---|
rules/tailwind.md | → | rules/tailwind.md | rule |
rules/frontend-css.md | → | rules/frontend-css.md | rule |
Dependencies
No dependencies.
Required by
No other module depends on this one.
Included in presets
Install this module
Agent prompt
Recommended for agent users -- hands the whole install off to your assistant.
Fetch https://ccgm.dev/modules/tailwind.md and install this module into my Claude Code setup.
Native plugin marketplace
One command via the native plugin marketplace -- additive, does not merge settings.json.
claude plugin install tailwind@ccgm
The marketplace path is additive, not a replacement: it installs commands, agents, and skills as native plugin components, but it does not perform the bash installer's deep settings.json merge, and it does not write the always-loaded global CLAUDE.md context. Rules are only injected via an opt-in SessionStart hook rather than being auto-loaded. Use the bash installer when those pieces matter to you.
Files
rule (2)
rules/tailwind.md
---
paths:
- "**/*.css"
- "**/tailwind.config.*"
- "**/postcss.config.*"
- "**/*.tsx"
- "**/*.jsx"
- "**/*.vue"
- "**/*.svelte"
- "**/*.astro"
---
# Tailwind CSS Design System
Patterns for building design systems with Tailwind CSS v4. Emphasizes CSS-first configuration and token-based architecture.
## CSS-First Configuration (v4)
Tailwind v4 uses CSS for configuration instead of `tailwind.config.ts`:
```css
@theme {
--color-primary: oklch(0.7 0.15 250);
--color-surface: oklch(0.98 0.01 250);
--spacing-page: 2rem;
--font-display: "Cal Sans", sans-serif;
}
```
Do NOT create `tailwind.config.ts` or `tailwind.config.js` in v4 projects. All configuration belongs in CSS.
## Design Token Hierarchy
Organize tokens in three layers:
1. **Primitive tokens** - raw values (colors, sizes, font families)
2. **Semantic tokens** - purpose-driven references (`text-primary`, `bg-surface`, `border-subtle`)
3. **Component tokens** - specific UI usage (`button-bg`, `card-radius`)
Use semantic token names, not visual descriptions. `text-primary` not `dark-gray`. `bg-surface` not `light-beige`.
## Color System
Use OKLCH color space for better perceptual uniformity:
```css
@theme {
--color-primary-50: oklch(0.97 0.02 250);
--color-primary-100: oklch(0.93 0.04 250);
--color-primary-500: oklch(0.65 0.15 250);
--color-primary-900: oklch(0.30 0.10 250);
}
```
- Define full color scales (50-950) for primary, neutral, and accent palettes
- Never use hardcoded hex or rgb values in component code
- Reference tokens exclusively: `bg-primary-500` not `bg-[#3b82f6]`
## Component Variants with CVA
Use Class Variance Authority for type-safe component variants:
```typescript
import { cva, type VariantProps } from "class-variance-authority";
const button = cva("inline-flex items-center justify-center rounded-md font-medium", {
variants: {
variant: {
primary: "bg-primary-500 text-white hover:bg-primary-600",
secondary: "bg-surface border border-subtle hover:bg-muted",
ghost: "hover:bg-muted",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
});
```
## Dark Mode
Use class-based dark mode with `@custom-variant`:
```css
@custom-variant dark (&:where(.dark, .dark *));
```
- Implement via a context-based theme provider
- Detect system preference with `prefers-color-scheme`
- Persist user choice to localStorage
- Test both modes for every component
## Responsive Patterns
- Mobile-first: write base styles for mobile, add breakpoint overrides for larger screens
- Use grid variants for responsive column layouts (1 column mobile, 2-3 tablet, 4-6 desktop)
- Use `size-*` shorthand when width and height are equal
- Use `gap-*` instead of `space-x-*` or `space-y-*`
## Animations
Define animations in `@theme` using native CSS `@keyframes`:
```css
@theme {
--animate-fade-in: fade-in 0.2s ease-out;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
```
Use `@starting-style` for entry animations on elements that appear dynamically.
## Migration Notes (v3 to v4)
| v3 Pattern | v4 Pattern |
|-----------|-----------|
| `tailwind.config.ts` | `@theme` in CSS |
| `theme.extend.colors` | `--color-*` custom properties |
| Plugin-based animations | `@keyframes` in `@theme` |
| `darkMode: 'class'` | `@custom-variant dark` |
| Separate `w-*` / `h-*` | `size-*` shorthand |
rules/frontend-css.md
---
paths:
- "**/*.css"
- "**/tailwind.config.*"
- "**/postcss.config.*"
---
# Frontend CSS Gotchas
## Tailwind v4: cursor: pointer Missing on Interactive Elements
**Problem**: Tailwind v4's preflight does NOT set `cursor: pointer` on `<button>` elements. Browsers default to `cursor: default` for buttons, so every clickable thing on the page looks non-interactive on desktop.
**Rule**: When starting any new project with Tailwind v4, add cursor: pointer base styles immediately - before writing any components.
### Pattern (put this in a shared CSS file imported by all apps)
```css
/* packages/ui/src/theme/base.css or equivalent */
@layer base {
button,
[role="button"],
[type="button"],
[type="reset"],
[type="submit"],
a[href],
label[for],
select,
summary {
cursor: pointer;
}
[disabled],
[aria-disabled="true"] {
cursor: not-allowed;
}
}
```
### Where to put it
- **Monorepo with shared UI package**: Add to `packages/ui/src/theme/base.css`, import in each app's CSS entry point after `@import 'tailwindcss'`
- **Single app**: Add directly to `src/index.css` or `src/globals.css` after `@import 'tailwindcss'`
- **Next.js**: Add to `app/globals.css` or `styles/globals.css`
### Import order matters
```css
@import 'tailwindcss';
@import './base.css'; /* cursor: pointer and other base resets */
@import './tokens.css'; /* design tokens */
```
The base import must come after `tailwindcss` so `@layer base` is available, but before component styles.
### Why this happens
Tailwind v4 changed their preflight philosophy - they removed the `cursor: pointer` override that existed in v3. The browser's native stylesheet sets `cursor: default` on `<button>`, which takes precedence unless explicitly overridden. This affects:
- All `<button>` elements (form submits, icon buttons, toggles)
- Custom interactive divs without a `cursor-pointer` class
- Select dropdowns, file inputs, etc.