npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

oxlint-tailwindcss

v0.4.0

Published

Tailwind CSS linting rules for oxlint

Readme

oxlint-tailwindcss

22 Tailwind CSS linting rules for oxlint. Built for Tailwind CSS v4 with auto-detection, typo suggestions, and autofixes.

Read the story behind this plugin: oxlint-tailwindcss: The Linting Plugin Tailwind v4 Needed

Highlights

  • Works out of the box — Auto-detects your Tailwind CSS entry point. Fully configurable when needed.
  • Monorepo-ready — Each package resolves its own design system automatically. Run oxlint once from the workspace root.
  • Fast — Native oxlint plugin with per-entry-point caching and content-based disk cache for monorepo deduplication.
  • Tailwind CSS v4 — Designed for v4 from day one.
  • Typo suggestionsitms-center → "Did you mean items-center?"
  • Conflict detection — Shows exactly which CSS properties conflict and which class wins.
  • Lightweight — Only 2 runtime dependencies: @tailwindcss/node and tailwindcss.
  • 22 rules — Correctness, style, complexity, and restriction rules with autofixes where possible.
  • Variable detection — Lints variables named className, classes, style automatically.
  • Customizable — Extend class detection with custom attributes, callees, tags, and variable patterns.
  • Component class support — Recognizes @layer components { .btn {} } in your CSS.

Installation

pnpm add -D oxlint-tailwindcss

Setup

Add the plugin to your .oxlintrc.json:

{
  "jsPlugins": ["oxlint-tailwindcss"],
  "rules": {
    // Correctness
    "tailwindcss/no-unknown-classes": "error",
    "tailwindcss/no-duplicate-classes": "error",
    "tailwindcss/no-conflicting-classes": "error",
    "tailwindcss/no-deprecated-classes": "error",
    "tailwindcss/no-unnecessary-whitespace": "error",
    "tailwindcss/no-dark-without-light": "warn",
    "tailwindcss/no-contradicting-variants": "warn",
    // Style
    "tailwindcss/enforce-canonical": "warn",
    "tailwindcss/enforce-sort-order": "warn",
    "tailwindcss/enforce-shorthand": "warn",
    "tailwindcss/enforce-logical": "off",
    "tailwindcss/enforce-physical": "off",
    "tailwindcss/enforce-consistent-important-position": "warn",
    "tailwindcss/enforce-negative-arbitrary-values": "warn",
    "tailwindcss/enforce-consistent-variable-syntax": "warn",
    "tailwindcss/consistent-variant-order": "warn",
    // Complexity
    "tailwindcss/max-class-count": "off",
    "tailwindcss/enforce-consistent-line-wrapping": "off",
    // Restrictions
    "tailwindcss/no-restricted-classes": "off",
    "tailwindcss/no-arbitrary-value": "off",
    "tailwindcss/no-hardcoded-colors": "warn",
    "tailwindcss/no-unnecessary-arbitrary-value": "warn",
  },
}

The plugin auto-detects your Tailwind CSS entry point. No configuration needed in most projects.

Auto-detection

The plugin searches for a CSS file containing a Tailwind import signal (@import "tailwindcss", @import 'tailwindcss', @import tailwindcss, @tailwind base) in these locations, walking upward from the linted file:

src/{name}.css       {name}.css            app/{name}.css
styles/{name}.css    style/{name}.css      css/{name}.css
assets/{name}.css    assets/css/{name}.css  resources/css/{name}.css

Where {name} is one of: app, globals, global, style, styles, index, main, tailwind, tailwindcss.

The search is monorepo-aware — it stops at package.json boundaries so each package resolves its own Tailwind config. If the signal isn't found directly in the CSS file, the auto-detector follows @import statements one level deep — supporting both relative paths and package imports (e.g. @import '@company/theme/tailwind.config.css').

If auto-detection doesn't find your CSS file, set entryPoint once in settings:

{
  "jsPlugins": ["oxlint-tailwindcss"],
  "settings": {
    "tailwindcss": {
      "entryPoint": "app/tailwind.css",
    },
  },
  "rules": {
    "tailwindcss/no-unknown-classes": "error",
    // ...
  },
}

The design system is loaded once per entry point and shared across all rules. In monorepos, each package resolves its own entry point automatically via package.json boundaries.

You can also override per rule if needed:

{
  "rules": {
    "tailwindcss/no-unknown-classes": ["error", { "entryPoint": "src/app.css" }],
  },
}

Resolution order: rule option > settings.tailwindcss.entryPoint > auto-detect.

For slow environments (large monorepos, CI), you can increase the design system loading timeout:

{
  "settings": {
    "tailwindcss": {
      "timeout": 60000, // milliseconds (default: 30000)
    },
  },
}

If no entry point is found (neither configured nor auto-detected), rules that require the design system (no-unknown-classes, no-conflicting-classes, no-deprecated-classes, enforce-canonical, enforce-sort-order, no-unnecessary-arbitrary-value, consistent-variant-order) are silently disabled. All other rules work without it.

Custom class detection

By default the plugin detects Tailwind classes in className/class attributes, 14 utility functions (cn, clsx, cva, tv, classed, etc.), tw tagged templates, and variables named className/classes/style.

You can extend these defaults via settings.tailwindcss. All values are additive — your custom entries are appended to the built-in defaults:

{
  "jsPlugins": ["oxlint-tailwindcss"],
  "settings": {
    "tailwindcss": {
      // Additional JSX attribute names to scan
      "attributes": ["xyzClassName", "classNames", "overlayClassName"],
      // Additional function names to scan
      "callees": ["myHelper"],
      // Additional tagged template tags to scan
      "tags": ["css"],
      // Additional regex patterns for variable names (as strings)
      "variablePatterns": ["^tw"],
    },
  },
  "rules": {
    "tailwindcss/no-unknown-classes": "error",
    // ...
  },
}

This applies to all 22 rules at once. For example, adding "classNames" to attributes makes every rule lint <Input classNames={{ root: "..." }} />.

To remove specific items from the built-in defaults, use exclude:

{
  "settings": {
    "tailwindcss": {
      "exclude": {
        // Stop scanning variables named "style" / "styles"
        "variablePatterns": ["^styles?$"],
        // Stop scanning a specific callee
        "callees": ["objstr"],
      },
    },
  },
}

For variablePatterns, exclusions match against the regex source (e.g. "^styles?$" removes the default /^styles?$/ pattern).

Supported patterns

The plugin extracts Tailwind classes from:

// JSX attributes
<div className="flex items-center" />
<div class="flex items-center" />

// Template literals
<div className={`flex ${condition ? "hidden" : ""}`} />

// Ternaries
<div className={active ? "bg-blue-500" : "bg-gray-200"} />

// Utility functions (cn, clsx, cx, cva, twMerge, twJoin, classnames, ctl, cc, clb, cnb, objstr)
cn("flex items-center", condition && "hidden")
clsx("flex", { "bg-red-500": isError })
twMerge("p-4", "p-2")

// cva() — full extraction: base, variants, compoundVariants
cva("flex items-center", {
  variants: {
    size: { sm: "text-sm p-2", lg: "text-lg p-4" },
  },
  compoundVariants: [
    { size: "sm", class: "font-medium" },
  ],
})

// tv() — full extraction: base, slots, variants (with slot objects), compoundVariants, compoundSlots
tv({
  base: "flex items-center",
  slots: { header: "p-4 font-bold", body: "p-2" },
  variants: {
    color: {
      primary: { header: "bg-blue-500", body: "text-blue-900" },
    },
  },
  compoundSlots: [
    { color: "primary", class: "border-blue-500" },
  ],
})

// classed() (tw-classed) — skips element type, extracts classes and cva-like config
classed("button", "flex items-center", {
  variants: {
    color: { primary: "bg-blue-500", secondary: "bg-gray-500" },
  },
})

// Object-valued JSX attributes (e.g. Mantine classNames prop)
<Input classNames={{ root: "flex items-center", input: "border-none" }} />

// Tagged templates
const styles = tw`flex items-center hover:bg-blue-500`

// Variable declarations (matched by name: className, classNames, classes, style, styles)
const className = "flex items-center"
const classes = condition ? "bg-blue-500" : "bg-gray-200"

Rules

Correctness

no-unknown-classes

Reports classes not recognized by Tailwind CSS. Includes typo suggestions.

// ❌ Bad
<div className="flex itms-center bg-blu-500" />
//                   ^^^^^^^^^^^
// "itms-center" is not a valid Tailwind class. Did you mean "items-center"?
//                                ^^^^^^^^^^
// "bg-blu-500" is not a valid Tailwind class. Did you mean "bg-blue-500"?

Options:

| Option | Type | Description | | ---------------- | ---------- | ------------------------------------- | | allowlist | string[] | Custom classes to allow | | ignorePrefixes | string[] | Prefixes to ignore (e.g. ["data-"]) |

Requires design system. No autofix.


no-duplicate-classes

Detects repeated classes in the same string. hover:flex and focus:flex are not considered duplicates (different variants).

// ❌ Bad
<div className="flex flex items-center" />

// ✅ Fixed
<div className="flex items-center" />

Autofix: Removes the duplicate.


no-conflicting-classes

Detects classes that set the same CSS property. Reports which property conflicts and which class wins.

// ❌ Bad
<div className="text-red-500 text-blue-500" />
// "text-red-500" and "text-blue-500" affect "color".
// "text-blue-500" takes precedence (appears later).

<div className="mt-2 mt-4" />
// "mt-2" and "mt-4" affect "margin-top".

Note: Shorthand vs longhand conflicts (e.g., p-4 vs px-2) are not currently detected. See Known limitations.

Requires design system. No autofix.


no-deprecated-classes

Detects classes deprecated in Tailwind CSS v4.

// ❌ Bad
<div className="flex-grow" />
// "flex-grow" is deprecated in Tailwind v4. Use "grow" instead.

// ✅ Fixed
<div className="grow" />

Deprecated class mappings:

| Deprecated | Replacement | | ------------------- | ---------------------- | | flex-grow | grow | | flex-grow-0 | grow-0 | | flex-shrink | shrink | | flex-shrink-0 | shrink-0 | | overflow-ellipsis | text-ellipsis | | decoration-slice | box-decoration-slice | | decoration-clone | box-decoration-clone |

Autofix: Replaces with the modern equivalent.


no-unnecessary-whitespace

Normalizes whitespace in class strings.

// ❌ Bad
<div className="  flex   items-center  " />

// ✅ Fixed
<div className="flex items-center" />

Autofix: Trims and collapses whitespace.


no-dark-without-light

Requires a base (light) utility when using the dark: variant on the same element.

// ❌ Bad — dark variant without base
<div className="dark:bg-gray-900" />
// "dark:bg-gray-900" uses the dark variant, but there is no base "bg-*" class.

// ✅ OK — has matching base
<div className="bg-white dark:bg-gray-900" />
<div className="text-black dark:text-white" />

Groups by utility prefix (bg-, text-, border-, etc.) — only checks that a base utility of the same type exists.

Options:

| Option | Type | Default | Description | | ---------- | ---------- | ---------- | ------------------------------------ | | variants | string[] | ["dark"] | Variant prefixes to check for a base |

No autofix.


no-contradicting-variants

Detects variant-prefixed classes that are redundant because the base class already applies unconditionally.

// ❌ Bad — dark:flex is redundant because flex already applies always
<div className="flex dark:flex" />
<div className="hidden hover:hidden" />

// ✅ OK — different values, both conditional, or different selector targets
<div className="text-white dark:text-black" />
<div className="hover:flex dark:flex" />
<div className="absolute after:absolute" />
<div className="shrink-0 [&>svg]:shrink-0" />

Only flags when the exact same utility exists both as base and with a conditional variant. Variants that change the selector target (pseudo-elements, child/descendant selectors, arbitrary selectors) are not flagged.

No options. No autofix.


Style

enforce-canonical

Suggests the canonical form of a class when a shorter equivalent exists. Only known classes are checked — arbitrary values are left as-is.

// ❌ Bad → ✅ Fixed
"-m-0"   → "m-0"
"-mt-0"  → "mt-0"
"-p-0"   → "p-0"

Requires design system. Autofix: Replaces with canonical form.


enforce-sort-order

Sorts classes according to Tailwind's official class order — identical to oxfmt and prettier-plugin-tailwindcss. Uses ds.getClassOrder() from the Tailwind CSS engine for exact results.

// ❌ Bad
<div className="text-red-500 flex items-center p-4" />

// ✅ Fixed
<div className="flex items-center p-4 text-red-500" />

In strict mode, classes are grouped by variant prefix, sorted within each group by DS sort order, and groups are ordered: no-variant first, then by variant priority.

// ❌ Bad (strict mode)
<div className="hover:text-red-500 p-4 hover:bg-blue-500 m-2" />

// ✅ Fixed
<div className="m-2 p-4 hover:bg-blue-500 hover:text-red-500" />

Options:

| Option | Type | Default | Description | | ------ | ------------------------- | ----------- | ----------- | | mode | "default" | "strict" | "default" | Sort mode |

Requires design system. Autofix: Reorders classes.


enforce-shorthand

Suggests shorthand classes when all axes have the same value.

// ❌ Bad → ✅ Fixed
"mt-2 mr-2 mb-2 ml-2"  → "m-2"
"mt-2 mb-2"             → "my-2"
"ml-2 mr-2"             → "mx-2"
"pt-4 pr-4 pb-4 pl-4"  → "p-4"
"pt-4 pb-4"             → "py-4"
"pl-4 pr-4"             → "px-4"
"w-full h-full"         → "size-full"
"rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg" → "rounded-lg"

Autofix: Replaces with shorthand.


enforce-logical

Converts physical properties to logical ones for LTR/RTL support.

// ❌ Bad → ✅ Fixed
"ml-4"    → "ms-4"
"mr-4"    → "me-4"
"pl-4"    → "ps-4"
"pr-4"    → "pe-4"
"left-0"  → "start-0"
"right-0" → "end-0"

Also converts border-l/r, rounded-l/r/tl/tr/bl/br, and scroll-ml/mr/pl/pr to their logical equivalents.

Autofix: Replaces with logical equivalent.


enforce-physical

The inverse of enforce-logical. Converts logical properties back to physical ones for consistency in LTR-only projects.

// ❌ Bad → ✅ Fixed
"ms-4"    → "ml-4"
"me-4"    → "mr-4"
"ps-4"    → "pl-4"
"pe-4"    → "pr-4"
"start-0" → "left-0"
"end-0"   → "right-0"

Autofix: Replaces with physical equivalent.


enforce-consistent-important-position

Enforces a consistent position for the ! (important) modifier — either prefix or suffix.

// ❌ Bad (default: suffix — Tailwind v4 canonical form)
<div className="!font-bold" />
<div className="hover:!text-red" />

// ✅ Fixed
<div className="font-bold!" />
<div className="hover:text-red!" />

Handles variants correctly — the ! is placed on the utility, not the variant prefix.

Options:

| Option | Type | Default | Description | | ---------- | ------------------------ | ---------- | ------------------------------- | | position | "prefix" | "suffix" | "suffix" | Where to place the ! modifier |

Note: The default is "suffix" to match Tailwind CSS v4's canonical form. The prefix form (!flex) is deprecated in v4. Using "prefix" may conflict with enforce-canonical, which also normalizes ! to the suffix position.

Autofix: Moves ! to the correct position.


enforce-negative-arbitrary-values

Moves the negative sign inside arbitrary value brackets for consistency.

// ❌ Bad
<div className="-top-[5px]" />
<div className="-translate-x-[10px]" />
<div className="hover:-mt-[8px]" />

// ✅ Fixed
<div className="top-[-5px]" />
<div className="translate-x-[-10px]" />
<div className="hover:mt-[-8px]" />

No options. Autofix: Moves the negative inside the brackets.


enforce-consistent-variable-syntax

Enforces consistent CSS variable syntax between Tailwind v4 shorthand bg-(--var) and explicit bg-[var(--var)].

// ❌ Bad (default: shorthand)
<div className="bg-[var(--primary)]" />
<div className="text-[var(--text-color)]" />

// ✅ Fixed
<div className="bg-(--primary)" />
<div className="text-(--text-color)" />

Does NOT convert complex expressions — only simple var(--name) wrappers:

// ✅ Left as-is (complex expression)
<div className="bg-[color-mix(in_srgb,var(--primary),transparent)]" />

Options:

| Option | Type | Default | Description | | -------- | ----------------------------- | ------------- | ----------------------- | | syntax | "shorthand" | "explicit" | "shorthand" | Which syntax to enforce |

Autofix: Converts between syntaxes.


consistent-variant-order

Enforces a consistent order for variant prefixes.

Uses the design system's variant order when available, falls back to a sensible static default. Only checks classes with 2+ variants.

Options:

| Option | Type | Default | Description | | ------- | ---------- | --------------------------- | ---------------------------- | | order | string[] | (DS order or builtin order) | Custom variant priority list |

Optionally uses design system. Autofix: Reorders variants.


Complexity

max-class-count

Warns when an element has too many Tailwind classes, suggesting extraction into a component or utility.

// ❌ Bad (with default max: 20)
<div
  className="flex items-center justify-between p-4 m-2 bg-white text-black
  rounded shadow border w-full h-10 gap-2 font-bold text-sm overflow-hidden
  cursor-pointer transition duration-200 opacity-50"
/>
// Too many Tailwind classes (21). Maximum allowed is 20.

Options:

| Option | Type | Default | Description | | ------ | -------- | ------- | --------------------------- | | max | number | 20 | Maximum classes per element |

No autofix — requires developer judgment on how to split.


enforce-consistent-line-wrapping

Warns when a class string exceeds the configured print width or classes-per-line limit.

// ❌ Bad (with default printWidth: 80)
<div className="flex items-center justify-between p-4 m-2 bg-white text-black rounded shadow-lg border w-full" />
// Class string is 97 characters long, exceeding the print width of 80.

// ❌ Bad (with classesPerLine: 3)
<div className="flex items-center justify-between p-4 m-2 bg-white" />
// Too many classes on a single line (7). Maximum allowed per line is 3.

Options:

| Option | Type | Default | Description | | ---------------- | -------- | ------- | ----------------------- | | printWidth | number | 80 | Max class string length | | classesPerLine | number | | Max classes per line |

Autofix: Only for classesPerLine with template literals.


Restrictions

no-restricted-classes

Blocks specific Tailwind classes by exact name or regex pattern with optional custom messages.

// With options: { classes: ["hidden"], patterns: [{ pattern: "^float-", message: "Use flexbox" }] }

// ❌ Bad
<div className="hidden" />        // "hidden" is restricted.
<div className="float-left" />    // "float-left" is restricted: Use flexbox

Options (required — rule is a no-op without them):

| Option | Type | Default | Description | | ---------- | ---------------------------------------------- | ------- | -------------------------- | | classes | string[] | [] | Exact class names to block | | patterns | Array<{ pattern: string, message?: string }> | [] | Regex patterns to match |

No autofix.


no-arbitrary-value

Prohibits arbitrary values ([...]) in Tailwind classes. Useful for teams that want to enforce strict design system adherence.

// ❌ Bad
<div className="w-[200px]" />
<div className="bg-[#ff0000]" />
<div className="hover:w-[200px]" />

// ✅ OK — arbitrary variants are NOT flagged
<div className="[&>svg]:w-4" />

Options:

| Option | Type | Default | Description | | ------- | ---------- | ------- | --------------------------------------------------- | | allow | string[] | [] | Utility prefixes to allow (e.g. ["bg-", "text-"]) |

No autofix.


no-hardcoded-colors

Flags hardcoded color values in arbitrary brackets. Encourages use of design tokens.

// ❌ Bad
<div className="bg-[#ff5733]" />
<div className="text-[rgb(255,0,0)]" />
<div className="border-[hsl(120,100%,50%)]" />
<div className="hover:bg-[#ff5733]" />

// ✅ OK — not a color utility
<div className="w-[200px]" />
<div className="tracking-[0.5em]" />

Detects hex, rgb/rgba, hsl/hsla, oklch, oklab, and other color function values inside [...] on color-related utility prefixes (bg-, text-, border-, ring-, shadow-, fill-, stroke-, etc.).

Options:

| Option | Type | Default | Description | | ------- | ---------- | ------- | ------------------------- | | allow | string[] | [] | Full class names to allow |

No autofix.


no-unnecessary-arbitrary-value

Detects arbitrary values that have a named Tailwind equivalent. The arbitrary form produces the exact same CSS, so the named class is preferred.

// ❌ Bad → ✅ Fixed
"h-[auto]"        → "h-auto"
"hover:h-[auto]"  → "hover:h-auto"

// ✅ OK — no named equivalent
"w-[200px]"
"bg-[#custom]"

Requires design system. Autofix: Replaces with named class.


Edge cases

The class parser correctly handles:

  • Nested brackets: bg-[url('https://example.com/img.png')]
  • Nested calc: h-[calc(100vh-var(--header-height))]
  • Arbitrary variants: [&>svg]:w-4, [&_p]:mt-2
  • Quoted values: content-['hello_world']
  • Important modifier: !font-bold
  • Negative values: -translate-x-1
  • Named groups/peers: group/sidebar, peer/input

Known limitations

  • enforce-canonical: Only classes in Tailwind's class list can be canonicalized. Some valid classes (e.g., grow-1, border-1) are not in the list and won't be converted. Arbitrary values are also not canonicalized.
  • no-conflicting-classes: Uses exact CSS property name matching. Shorthand vs longhand conflicts (e.g., p-4 vs px-2 where padding conflicts with padding-left) are not detected.
  • no-dark-without-light: Groups by utility prefix heuristic. May not perfectly match all multi-part utility prefixes.
  • no-unnecessary-arbitrary-value: Only detects equivalences for classes with a single CSS property. Multi-property utilities may have arbitrary forms that aren't detected.
  • Component classes: Only first-level @import relative paths are followed. Deeply nested imports or absolute paths are not resolved.

Requirements

  • Node.js >= 20
  • Tailwind CSS v4
  • oxlint >= 1.43.0

License

MIT