Fix Knip False Positives with Tailwind v4

3 min read View on GitHub

What is Knip?

Knip helps declutter JavaScript and TypeScript projects by detecting dead exports, unused files, and dependencies. It does this intelligently using a plugin system modeled after real-world tooling and frameworks. This fine-grained approach results in an accurate representation of dead code.

To get started with Knip, run:

Terminal window
pnpm create @knip/config

Add a script to your package.json:

package.json
{
"scripts": {
"knip": "knip"
}
}

Or just run it directly:

Terminal window
pnpx knip

Out of the box, Knip technically works with Tailwind CSS v4, but not perfectly. Its lack of support for Tailwind’s new @plugin and @import directives can cause false positives.

What changed in Tailwind CSS v4?

Tailwind CSS v4 introduced a new configuration approach that is CSS-first, which allows for imports and plugins. However, Knip doesn’t scan these directives by default, which leads to false positives. For example, if you have a Tailwind CSS file like this:

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "daisyui";
@plugin "@iconify/tailwind4";

When you run knip, you might see output like this:

Terminal window
Unused devDependencies (4)
@iconify/tailwind4 package.json:29:6
@tailwindcss/typography package.json:32:6
daisyui package.json:36:6
tailwindcss package.json:45:6
error: script "knip" exited with code 1

One quick fix is to ignore these dependencies in your Knip configuration file:

knip.config.ts
import type { KnipConfig } from "knip";
export default {
ignoreDependencies: [
"@iconify/tailwind4",
"@tailwindcss/typography",
"daisyui",
"tailwindcss",
],
} satisfies KnipConfig;

We use satisfies KnipConfig to ensure our config matches the expected structure. It’s a TypeScript safety check, not required for runtime.

Knip supports a custom compilers configuration option, and with a bit of regex magic, we can teach it to understand Tailwind’s directives like @import and @plugin.

How to configure Knip for Tailwind CSS v4?

knip.config.ts
import type { KnipConfig } from "knip";
export default {
compilers: {
css: (text: string) => {
// Converts @import and @plugin directives into JS-style imports
return [...text.matchAll(/@(?:import|plugin)\s+["']([^"']+)["']/g)]
.map(([_, dep]) => `import "${dep}";`)
.join("\n");
},
},
} satisfies KnipConfig;

And if you’ve modified knip’s project field, be sure to include .css files as well:

knip.config.ts
import type { KnipConfig } from "knip";
export default {
project: "**/*.{ts,tsx,css}",
} satisfies KnipConfig;

This prevents false positives, keeps your reports clean, and avoids accidentally removing real dependencies.

How does this work?

This configuration defines a custom compiler for CSS files. It looks for @import and @plugin directives using a regular expression and transforms them into JavaScript-style import statements that Knip can understand and analyze like regular module imports.

This borrows from Knip’s own css compiler example.

matchAll is used to find all matches of a regular expression in a string.

The regex pattern @(?:import|plugin)\s+["']([^"']+)["'] breaks down as:

  • @(?:import|plugin) - Matches either @import or @plugin
  • \s+ - Matches one or more whitespace characters
  • ["']([^"']+)["'] - Captures the dependency name inside quotes

In the earlier example, it would return:

[
['@import "tailwindcss"', "tailwindcss"],
['@plugin "@tailwindcss/typography"', "@tailwindcss/typography"],
['@plugin "daisyui"', "daisyui"],
['@plugin "@iconify/tailwind4"', "@iconify/tailwind4"],
];

You can further learn about regex at regex101.

Then map transforms each match into an import statement using the captured dependency name, and the join("\n") combines them into a single string to make it look like valid JavaScript/TypeScript code.

Once that’s in place, running knip will no longer flag these dependencies:

Terminal window
✂️ Excellent, Knip found no issues.

Tailwind’s new config is great, but Knip doesn’t know what to do with @plugin or @import by default. This setup bridges that gap and keeps your reports clean.

Questions or Feedback?

I'd love to hear your thoughts, questions, or feedback about this post.

Reach me via email or LinkedIn.

Last updated on

Back to all posts