Use when migrating Tailwind v3 to v4, configuring CSS-first @theme tokens, debugging Oxide engine errors, dealing with the absence of tailwind.config.js, container queries (@container) usage, layer cascade problems, or "dynamic class names not generating". Triggers: switching from `@tailwind base` to `@import "tailwindcss"`, RSC/Next.js streaming + CSS ordering, dark-mode strategy choices, custom variants via @variant, content-scanning glob tuning. NOT for Tailwind v3 (different config model)...
Scanned 9/11/2026
Install to Claude Code
npx -y skills add curiositech/windags-skills --skill tailwind-v4-expert --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Tailwind V4 Expert?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/curiositech-tailwind-v4-expert)More formats (shields.io, HTML) on the badges page.
---
name: tailwind-v4-expert
description: 'Use when migrating Tailwind v3 to v4, configuring CSS-first @theme tokens, debugging Oxide engine errors, dealing with the absence of tailwind.config.js, container queries (@container) usage, layer cascade problems, or "dynamic class names not generating". Triggers: switching from `@tailwind base` to `@import "tailwindcss"`, RSC/Next.js streaming + CSS ordering, dark-mode strategy choices, custom variants via @variant, content-scanning glob tuning. NOT for Tailwind v3 (different config model), CSS-in-JS frameworks, vanilla CSS architecture (BEM/SMACSS), or react-native styling.'
category: Frontend & UI
tags:
- tailwind
- css
- design-tokens
- oxide
- container-queries
- frontend
---
# Tailwind v4 Expert
Tailwind v4 is a different tool than v3. The PostCSS-based JIT engine is gone, replaced by Oxide (Rust). The JS config file is gone, replaced by CSS-first `@theme` blocks. Plugins from v3 mostly need rewriting. If you're migrating, expect to rebuild — not patch — your config layer.
## When to use
- Migrating a v3 project (look for `@tailwind base; @tailwind components; @tailwind utilities;`).
- Need design tokens (colors, fonts, breakpoints) consumed by both Tailwind and raw CSS.
- Container queries — designing components that adapt to their container, not the viewport.
- Custom variants beyond what v4 ships (`@variant focus-within-sibling`, etc.).
- Dynamic class names disappearing from the build (Oxide can't see what isn't statically present).
- RSC + streaming + Tailwind ordering bugs.
## Core capabilities
### The v4 import model
```css
/* app/globals.css */
@import "tailwindcss";
/* Optional: scan files outside the default content roots. */
@source "../../packages/ui/src/**/*.{ts,tsx}";
@source "../../node_modules/@my-org/icons/dist/**/*.js";
```
There is no `tailwind.config.js`. There is no `content` array. `@source` adds globs to the default scan root (the file's dir + project conventions). Oxide statically scans these for class strings.
### `@theme` — design tokens are CSS now
```css
@theme {
--color-brand-50: oklch(97% 0.02 250);
--color-brand-500: oklch(60% 0.18 250);
--color-brand-900: oklch(20% 0.10 250);
--font-display: "Söhne", "Inter", sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--breakpoint-3xl: 120rem;
--spacing-128: 32rem;
}
```
These tokens generate utilities (`text-brand-500`, `font-display`, `3xl:`, `w-128`) AND are accessible as `var(--color-brand-500)` in raw CSS. One source of truth.
Namespaces that auto-generate utilities:
| Token prefix | Generates |
|--------------|-----------|
| `--color-*` | `text-*`, `bg-*`, `border-*`, `outline-*`, etc. |
| `--font-*` | `font-*` |
| `--text-*` | `text-*` (font-size; pair with `--text-*--line-height`) |
| `--spacing-*` | `m-*`, `p-*`, `w-*`, `h-*`, `gap-*`, etc. |
| `--breakpoint-*` | `sm:`, `md:`, `lg:`… variants |
| `--radius-*` | `rounded-*` |
| `--shadow-*` | `shadow-*` |
### `@layer` cascade
```css
@layer base {
body { @apply font-sans text-zinc-900; }
}
@layer components {
.btn { @apply rounded-lg px-4 py-2 font-semibold; }
}
/* Custom layer between components and utilities */
@layer my-overrides {
.prose img { border-radius: var(--radius-lg); }
}
```
Layers cascade: base → components → utilities → arbitrary user layers. Tailwind defines its own at `@layer base, components, utilities;` automatically; you rarely need to redeclare them.
### Custom variants
```css
@variant pointer (@media (pointer: fine));
@variant hocus (&:hover, &:focus-visible);
@variant aria-current (&[aria-current="page"]);
```
Then: `<a class="text-zinc-500 hocus:text-brand-500 aria-current:text-brand-700">`.
### Container queries
```html
<aside class="@container">
<article class="grid grid-cols-1 @lg:grid-cols-2 @3xl:grid-cols-3">
```
Named containers if the same element has nested ones:
```html
<main class="@container/main">
<aside class="@container/sidebar">
<div class="@md/sidebar:hidden @lg/main:block">
```
Container query breakpoints come from `--container-*` tokens (separate namespace from `--breakpoint-*`).
### Dark mode
Three strategies; pick one:
```css
/* 1. System preference only — easiest, no JS. */
@variant dark (@media (prefers-color-scheme: dark));
/* 2. Class-based — supports manual toggle. */
@variant dark (&:where(.dark, .dark *));
/* 3. data-attribute — same idea, semantic. */
@variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
```
Then theme-shift colors via `light-dark()` or via separate token blocks:
```css
@theme {
--color-bg: oklch(99% 0 0);
}
@layer base {
:where(.dark) {
--color-bg: oklch(15% 0 0);
}
}
```
### Next.js App Router specifics
- Put `@import "tailwindcss"` and `@theme` in `app/globals.css`.
- Import that file once in `app/layout.tsx`.
- For RSC streaming, ensure the CSS link is in `<head>` (Next does this automatically when imported from layout).
- Server Components can use Tailwind classes directly; no client boundary needed.
- If you ship a workspace `@my-org/ui` package, add `@source "../../packages/ui/**/*.{ts,tsx}"` to your globals so Oxide scans it.
## Anti-patterns
### Dynamic class names that Oxide can't see
**Symptom:** `text-${color}-500` works in dev, missing in production.
**Diagnosis:** Oxide is a static scanner — it sees string literals, not template strings.
**Fix:** Map the dynamic value to a literal class:
```ts
const variants = { primary: 'bg-brand-500', danger: 'bg-red-500' } as const;
<button class={variants[kind]} />
```
Or use CSS variables for the dynamic part: `style={{ '--accent': color }} class="bg-[--accent]"`.
### Migrating but keeping `tailwind.config.js`
**Symptom:** v4 builds, but custom theme tokens don't appear; warnings about unknown directives.
**Diagnosis:** v4 ignores tailwind.config.js. The presence of one suggests an incomplete migration.
**Fix:** Move `theme.extend` values into `@theme`, plugins into `@variant`/CSS, content into `@source`. Delete the file. Run `npx @tailwindcss/upgrade@latest` for an automated first pass.
### `@apply` for everything
**Symptom:** CSS bundle bigger than v3, "components" file is 2000 lines of `@apply`.
**Diagnosis:** Each `@apply` inlines utilities; deeply-applied components don't dedupe well.
**Fix:** Apply once at the component boundary; compose via class strings (`clsx`/`cva`) for variants.
### Forgetting `@source` for files outside the project root
**Symptom:** UI library shipped from a sibling workspace — its classes don't appear in production CSS.
**Diagnosis:** Default scan covers the project's own files. Workspace deps need explicit `@source`.
**Fix:** `@source "../../packages/ui/src/**/*.{ts,tsx}"` (relative to the CSS file, not the project root).
### PurgeCSS-style mental model
**Symptom:** Engineers add classes to a "safelist" that doesn't exist.
**Diagnosis:** v4 doesn't purge — it generates. There's no allowlist; if the class isn't in a scanned file, it doesn't exist.
**Fix:** Use a stub component or comment that contains the literal classes:
```ts
// Tailwind safelist (do not delete): bg-red-500 bg-green-500 bg-blue-500
const STATUS_COLORS = { error: 'bg-red-500', ok: 'bg-green-500', info: 'bg-blue-500' };
```
### v3 plugins copy-pasted into v4
**Symptom:** `Plugin "tailwindcss/typography" failed to load.`
**Diagnosis:** v4's plugin API is incompatible with most v3 plugins.
**Fix:** Use the v4-native plugin (`@tailwindcss/typography` v0.6+ for v4), or rewrite the plugin's effects with `@variant` / `@layer components` / custom utilities.
## Quality gates
- [ ] No template-literal class strings without an explicit safelist or CSS-variable fallback.
- [ ] `tailwind.config.js` deleted (or absent from a fresh project).
- [ ] Every workspace dep with Tailwind classes has an `@source` glob.
- [ ] Dark-mode strategy documented in one place; not scattered across components.
- [ ] CSS bundle <50KB gzipped for a typical app; <30KB for a marketing page.
- [ ] Container queries used wherever a component is reused at different widths (cards, sidebars).
- [ ] Theme tokens accessible from raw CSS (`var(--color-brand-500)`) and from utilities.
## NOT for
- **Tailwind v3** — different config model, different engine.
- **CSS-in-JS** (Emotion, styled-components) — pair with css-in-js-architect skill.
- **Vanilla CSS architecture** (BEM, SMACSS, ITCSS) — different paradigm.
- **React Native** — Tailwind variants for RN exist but have different constraints.
- **CSS modules / vanilla-extract** — competing solutions.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!