@evilmartians/design-lint

中文文档
0.2.0发布于 2026/10/13MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2468

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

原始使用文档
文档目录13 节
  1. Setup
  2. Presets
  3. Keeping an existing .oxlintrc.json
  4. Rules
  5. Basic
  6. Strict
  7. Configuration
  8. Integration report
  9. Limits
  10. Requirements
  11. ESLint
  12. Contributing
  13. License

@evilmartians/design-lint

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

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

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.

Presets

Choose a preset with the preset option:

  • "strict" (default) enables all rules, including checks for token naming, themes, and component variants. It requires componentSources; pass [] if you have no component library.
  • "basic" enables checks that require only token stylesheets. Omit componentSources; this preset rejects it.

See Rules for the rules in each preset and their requirements.

await designLint({
  preset: "basic",
  tokenFiles: ["src/styles.css"],
});

You can enable individual strict rules with basic; see Configuration.

Keeping an existing .oxlintrc.json

Oxlint refuses to start when oxlint.config.ts and .oxlintrc.json sit in the same directory, and the setup above needs the TypeScript config. To keep your JSON config as it is, rename it (to oxlint.base.json, say) and merge it in:

import { defineConfig, type OxlintConfig } from "oxlint";
import { designLint } from "@evilmartians/design-lint/preset";
import json from "./oxlint.base.json" with { type: "json" };

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

export default defineConfig({
  ...base,
  jsPlugins: [...(base.jsPlugins ?? []), ...design.jsPlugins],
  rules: { ...base.rules, ...design.rules },
});

Merge it rather than passing it through extends: Oxlint rejects relative jsPlugins paths in a config that arrives through extends.

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

Basic

Enabled in both presets. These rules require only token stylesheets.

RuleWhat it reports
no-raw-colorLiteral colors such as #f00, rgb(...), red, and bg-[#f00]
no-spectral-colorTailwind palette classes such as bg-red-500
no-undefined-tokenToken-like color classes that generate no CSS
no-style-colorColor applied through React's style prop
no-useless-hoverHover styles on elements that are not interactive
no-arbitrary-typographyArbitrary font sizes, font weights, and letter spacing such as text-[13px]
no-arbitrary-radiusArbitrary corner radii such as rounded-[6px]

Strict

Enabled only in strict, alongside all basic rules. These checks rely on the conventions below.

RuleWhat it reportsRequired convention
token-constraintsValid tokens used in the wrong role, such as bg-muted-foregroundTokens named by role: *-foreground for text, *-hover for hover states
no-dark-variantLocal theme branches written with dark: or light-dark()Themes defined in token stylesheets
no-opacity-modifierColor classes with opacity modifiers such as bg-primary/50Named tokens for translucent colors
no-component-color-overrideColor classes passed to design-system components through classNameComponent variants that control appearance

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

Configuration

Override rule options or enable additional rules in your Oxlint config. Keep ...design.rules to preserve the preset’s rules:

const design = await designLint({
  preset: "basic",
  tokenFiles: ["src/styles.css"],
});

export default defineConfig({
  ...design,
  rules: {
    ...design.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.

Integration report

To see how much of the design system a project leaves unchecked, run this from where you run oxlint:

npx @evilmartians/design-lint report
design-lint report — oxlint.config.ts

Disabled rules (1 of 12)
  no-opacity-modifier  off

Silenced (49)
  no-raw-color             34
  no-dark-variant          12
  no-spectral-color        2
  (all rules, none named)  1

Ignored paths (2)
  src/legacy/**      ignorePatterns
  src/brand/art.tsx  no-raw-color
  • Disabled rules are rules set to off or omitted from your config. This includes strict rules when using basic, and no-component-color-override when componentSources: [].
  • Silenced adds line-disable rule mentions to recorded bulk suppression counts, broken down by rule, in files Oxlint lints. Each oxlint-disable-line, oxlint-disable-next-line, or equivalent eslint- comment contributes one per named design rule. A comment naming no rules contributes one on its own line. Bulk counts come from oxlint-suppressions.json, read from the directory Oxlint runs in. The total is an indicator of suppression use: a line comment can hide several violations or none, and recorded bulk counts can become stale.
  • Ignored paths lists the config's ignorePatterns and files containing a block /* oxlint-disable */ or /* eslint-disable */ comment. Comment entries show the design rules named, or "all rules" when none are named. A block disable can cover only part of a file, for example when followed by an enable comment; its entry does not mean the entire file is unchecked.

It reads oxlint.config.ts the way Oxlint finds it. Pass --config <file> and paths to match how you run oxlint.

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 4.1.18+

The package uses @tailwindcss/node to read the same design system your Tailwind build uses. It takes the copy your project resolves, so installing a newer one alongside an older Tailwind does not help: upgrade the project's Tailwind. 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 an ESLint v9 flat config. Add three things to your existing eslint.config.mjs:

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

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

// 2. Then load the plugin.
const designPlugin = (await import("@evilmartians/design-lint/oxlint")).default;

export default [
  // ...your existing config

  // 3. Turn the rules on for your component files.
  {
    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.

The rules use whatever parser your config already sets for these files, so it must read JSX, and TypeScript if you use it.

Contributing

See CONTRIBUTING.md.

License

MIT

版本与发布记录

@evilmartians/design-lint 共发布 5 个版本,首次发布于 2026/09/16;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.0latest2026/10/131 个月前
0.1.52026/10/051 个月前
0.1.22026/09/231 个月前
0.1.12026/09/211 个月前
0.1.02026/09/161 个月前

关于 @evilmartians/design-lint 的常见问题

@evilmartians/design-lint 是什么?

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

如何安装 @evilmartians/design-lint?

在项目目录运行 npm install @evilmartians/design-lint,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:oxlint(>=1.82.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 design-lint,可以用 npx design-lint 直接运行。

@evilmartians/design-lint 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@evilmartians/design-lint 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@evilmartians/design-lint 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.18.0 || >=23.6.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@evilmartians/design-lint 可以商用吗?

@evilmartians/design-lint 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@evilmartians/design-lint 的下载量有多少?

根据 npm 官方下载统计,@evilmartians/design-lint 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,468 次,日均约 82 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@evilmartians/design-lint 还在维护吗?

最新版本 0.2.0 发布于 2026/10/13(1 个月前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/09/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。