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

@evilmartians/design-lint

v0.1.0

Published

Design-token lint rules for Tailwind v4 codebases, for Oxlint and ESLint

Downloads

75

Readme

@evilmartians/design-lint

Design-token lint rules for Tailwind v4 projects using Oxlint.

The rules keep color decisions inside your design system. They catch hard-coded colors, Tailwind palette colors, local dark-mode branches, invalid token names, and component color overrides.

npm install --save-dev @evilmartians/design-lint oxlint

Setup

Create oxlint.config.ts:

import { defineConfig } from "oxlint";
import { designLint } from "@evilmartians/design-lint/preset";

export default defineConfig(
  await designLint({
    tokenFiles: ["src/styles.css"],
    componentSources: ["@/components/ui/*"],
  }),
);

tokenFiles are the stylesheets that define your --color-* tokens.

componentSources are the import paths for your design-system components. Write them exactly as they appear in your code. For example, if your app imports #/components/ui/button, use #/components/ui/*, not @/components/ui/*.

If the project has no component library, pass componentSources: []. That turns off no-component-color-override.

Do not keep both oxlint.config.ts and .oxlintrc.json. Oxlint auto-discovers the TypeScript config.

Example output:

src/App.tsx:5:46: error design(no-spectral-color): bg-red-500 — spectral color class; use bg-danger instead
src/App.tsx:8:21: error design(no-undefined-token): text-nonesuch generates no CSS — nonesuch is not defined; check the spelling, or add --color-nonesuch to your token stylesheet

Rules

| Rule | What it reports | | --- | --- | | no-raw-color | Literal colors such as #f00, rgb(...), red, and bg-[#f00] | | no-spectral-color | Tailwind palette classes such as bg-red-500 | | no-undefined-token | Token-like color classes that generate no CSS | | token-constraints | Valid tokens used in the wrong role, such as bg-muted-foreground | | no-style-color | Color applied through React's style prop | | no-opacity-modifier | Color classes with opacity modifiers such as bg-primary/50 | | no-dark-variant | Local theme branches written with dark: or light-dark() | | no-useless-hover | Hover styles on elements that are not interactive | | no-component-color-override | Color classes passed to design-system components through className |

Each rule has a short guide in docs/rules: what it reports, what it allows, and how to fix it.

Configuration

The preset gives every rule a recommended default. You can override individual rules in the usual Oxlint way:

export default defineConfig({
  ...(await designLint({
    tokenFiles: ["src/styles.css"],
    componentSources: ["@/components/ui/*"],
  })),
  rules: {
    "design/no-dark-variant": ["error", { flagNonColorUtilities: false }],
  },
});

When you restate a rule, you replace the options the preset gave it. If you only want to change severity, keep any required options with it. This matters most for no-component-color-override, because componentSources has no safe default.

Limits

These rules are intentionally static. They do not run your app or follow values across files.

Known limits:

  • CSS files are not linted yet. Classes in @apply and colors in CSS declarations are out of scope for now.
  • Dynamically assembled classes are not checked. For example, `bg-${tone}` is too ambiguous to validate.
  • Most rules check strings where they are written, not where a value eventually flows.
  • no-undefined-token only checks clear class-list positions such as className, class helper calls, cva() and tv().

Requirements

  • Node 22.18+ or 23.6+
  • Oxlint 1.82+
  • Tailwind v4

The package uses @tailwindcss/node to read the same design system your Tailwind build uses. If your project already uses Tailwind through Vite, PostCSS, or the Tailwind CLI, it is usually already installed. Otherwise add it:

npm install --save-dev @tailwindcss/node

ESLint

The rules can also run in ESLint v9 flat config. Resolve the design system first, then import the plugin:

// eslint.config.mjs
import { designLint } from "@evilmartians/design-lint/preset";

const design = await designLint({
  tokenFiles: ["src/styles.css"],
  componentSources: ["@/components/ui/*"],
});

const designPlugin = (await import("@evilmartians/design-lint/oxlint")).default;

export default [
  {
    files: ["**/*.{js,jsx,ts,tsx}"],
    plugins: { design: designPlugin },
    rules: design.rules,
  },
];

Use a dynamic import() for the plugin. The designLint() call prepares the token data the rules need, and the plugin reads it when it loads.

Contributing

See CONTRIBUTING.md.

License

MIT