You’ve upgraded your dependencies to Tailwind CSS v4, expecting the promised performance boost from the new Oxide engine. Instead, your build finishes instantly but your styles are missing, or your console is flooded with Unknown at rule @tailwind errors.
Tailwind v4 represents a paradigm shift from a JavaScript-configured utility library to a native CSS-first build tool. The breakage occurs because v4 deprecates the proprietary directives and JavaScript configuration reliance you’ve used for years.
Here is why your build is breaking and the rigorous path to fixing it.
The Root Cause: Architecture Shift
In v3, Tailwind relied on PostCSS to scan your tailwind.config.js, generate an Abstract Syntax Tree (AST), and inject CSS via the @tailwind directives (base, components, utilities).
In v4, the architecture is inverted:
- Native CSS Resolution: The core directives are replaced by standard CSS
@importstatements. - No Default JS Config: The engine no longer automatically looks for
tailwind.config.jsfor theme values. It expects configuration to exist within the CSS itself via CSS variables. - Linter Lag: Tools like VS Code’s native CSS validator and Stylelint do not inherently recognize the new v4 syntax or the legacy
@tailwinddirectives if the PostCSS context is missing.
The Fix
We will migrate a standard Vite-based project to the v4 architecture. This eliminates the "Unknown at rule" errors and restores your styles.
1. Update the Build Pipeline
Tailwind v4 is optimized to run as a dedicated Vite plugin rather than a generic PostCSS plugin. This provides the performance gains associated with the new engine.
File: vite.config.ts
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react'; // assuming React, irrelevant for the fix
export default defineConfig({
plugins: [
// The Tailwind plugin must come before framework plugins
tailwindcss(),
react(),
],
});
2. The CSS-First Entry Point
Delete your old directives. The @tailwind at-rule is the primary trigger for "Unknown at rule" errors in v4 if not strictly shimmed. We switch to the native CSS import syntax.
File: src/index.css (or app.css)
/* ❌ DELETE THIS (v3 Syntax) */
/* @tailwind base; */
/* @tailwind components; */
/* @tailwind utilities; */
/* ✅ ADD THIS (v4 Syntax) */
@import "tailwindcss";
/*
Configuration is now handled here, not in tailwind.config.js.
The @theme block exposes standard CSS variables as Tailwind utilities.
*/
@theme {
/* This replaces theme.extend.fontFamily */
--font-display: "Satoshi", "sans-serif";
/* This replaces theme.extend.colors */
--color-brand-primary: oklch(59.69% 0.156 49.77);
--color-brand-secondary: oklch(85.32% 0.12 49.77);
/* This replaces theme.extend.spacing */
--spacing-128: 32rem;
}
/*
Usage in HTML remains the same:
<div class="font-display text-brand-primary p-128">
*/
3. Fixing the IDE Warnings ("Unknown At Rule")
Even with the build working, VS Code may flag @import "tailwindcss" or @theme as errors because standard CSS specs don't officially support them yet.
Option A: The VS Code Setting Fix (Recommended) Force VS Code to ignore unknown CSS rules rather than validating strict W3C compliance for Tailwind files.
Create or update .vscode/settings.json:
{
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"files.associations": {
"*.css": "tailwindcss"
}
}
Option B: Stylelint Configuration If you enforce linting in your CI/CD pipeline, you must update your Stylelint configuration to allow the v4 specifics.
File: .stylelintrc.json
{
"extends": ["stylelint-config-standard"],
"rules": {
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["import", "theme", "utility", "apply"]
}
],
"import-notation": null
}
}
4. Handling Legacy JavaScript Config
If you have a massive tailwind.config.js and cannot migrate everything to CSS variables immediately, you can import the JS config into your CSS. This bridges the gap between v3 and v4.
File: src/index.css
@import "tailwindcss";
/* Point strictly to your legacy config */
@config "../tailwind.config.js";
Note: This detects the file, but v4 is stricter. Ensure your content array in the JS config is clean, though v4 mostly detects content files automatically now.
Why This Works
The Move to @import
By using @import "tailwindcss";, we leverage the CSS loader's native ability to resolve node modules. The @tailwindcss/vite plugin intercepts this import. Instead of reading a JS file to determine what CSS to generate, it treats the CSS file as the source of truth.
The @theme Block
In v3, configuration was opaque JavaScript. In v4, configuration is CSS variables. When you define --color-brand: red inside @theme, Tailwind's internal engine registers brand as a token in its utility generator. This creates text-brand, bg-brand, and border-brand automatically.
This removes the serialization overhead of passing data between Node.js (config) and the CSS generation engine, resulting in the sub-millisecond hot module replacement (HMR) times seen in v4.
Conclusion
The "Unknown At Rule" error is a symptom of tooling expecting the old PostCSS workflow. By switching to the @tailwindcss/vite plugin and adopting the CSS-first @theme configuration, you align your project with the intended architecture of v4. Treat your CSS file as the configuration engine, and the errors will resolve.