Design Token Lint

Type to search...

to open search from anywhere

使用例

作成2026年4月10日更新2026年4月10日Takeshi Takatsudo

タイトトークン戦略を使ったデザイントークン適用の具体的な例。

実際のプロジェクトで使える設定と使用パターン — 汎用的なプレースホルダーではなく、具体的なトークンを使います。

このツールが解決する問題

Tailwind のデフォルトの spacing スケールには30以上の数値ステップがあります(p-1p-2p-3p-4p-96)。あらゆる値が有効な状態では、開発者はそれぞれ微妙に異なる値を選んでしまいます:

// 開発者 A のコンポーネント — p-4 を使用
<div className="p-4 bg-gray-200">

// 開発者 B のコンポーネント — 「中くらいのパディング」のつもりで p-5 を使用
<div className="p-5 bg-gray-100">

// 開発者 C のコンポーネント — p-6 を使用
<div className="p-6 bg-gray-300">

3つともコンパイルは通ります。UIは少しずつズレていきます。

design-token-lint はこれらの数値ユーティリティを検出し、プロジェクト定義のセマンティックトークンのみが使われるようにします。1つのlintルールで、全体のスペーシングが統一されます。

具体的なトークンシステム

このページの例では、実際のトークンシステムを使います — このドキュメントサイト自体が使っているものと同じです。

スペーシングトークン

単一の数値スケールの代わりに、2つのセマンティック軸を使います:

水平スペーシングhsp-*)— インラインギャップ、水平方向のパディング:

トークン用途
hsp-2xs2pxタイトなインラインスペーシング
hsp-xs6pxコンパクトなインライン
hsp-sm8px小さなパディング
hsp-md12pxデフォルトのギャップ
hsp-lg16px標準的なパディング
hsp-xl24px余裕のあるパディング
hsp-2xl32px大きなパディング

垂直スペーシングvsp-*)— セクションギャップ、垂直方向のパディング:

トークン用途
vsp-2xs7pxタイトなギャップ
vsp-xs14px小さなギャップ
vsp-sm20pxコンパクトなギャップ
vsp-md24px標準的なギャップ
vsp-lg28pxセクションギャップ
vsp-xl40px大きなセクションギャップ
vsp-2xl56pxページレベルのギャップ

カラートークン

パレットのシェードではなく、セマンティックな名前を使います:

トークンTailwindクラス意味
surfacebg-surfaceカード・サイドバーの背景
mutedtext-muted二次的な、目立たないテキスト
accentbg-accentプライマリのインタラクティブカラー
accent-hoverbg-accent-hoverアクセントのホバー状態
code-bgbg-code-bgインラインコードの背景
code-fgtext-code-fgインラインコードのテキスト
successtext-success成功状態
dangertext-dangerエラー・破壊的な操作
warningtext-warning注意状態
infotext-info情報提供

これらはグローバルCSSの @theme に登録されています — bg-gray-200bg-blue-600 はそのトークンが存在しないため使用できません。使おうとするとlint違反になります。

リンターが検出するもの

このトークンシステム用の設定:

{
  "prohibited": [
    "p-{n}", "px-{n}", "py-{n}", "pt-{n}", "pr-{n}", "pb-{n}", "pl-{n}",
    "m-{n}", "mx-{n}", "my-{n}", "mt-{n}", "mr-{n}", "mb-{n}", "ml-{n}",
    "gap-{n}", "gap-x-{n}", "gap-y-{n}",
    "space-x-{n}", "space-y-{n}",
    "bg-{color}-{shade}",
    "text-{color}-{shade}",
    "border-{color}-{shade}"
  ],
  "allowed": ["p-0", "m-0", "gap-0", "p-px"],
  "patterns": ["src/**/*.{tsx,jsx,astro}"],
  "ignore": ["**/*.test.*", "**/*.stories.*"],
  "suggestionSuffix": "use semantic token (hsp-*/vsp-*) or arbitrary value"
}

リンターが報告する違反:

クラス理由修正
p-4p-{n} にマッチp-hsp-lg(16px 標準パディング)
gap-2gap-{n} にマッチgap-hsp-sm(8px 小さなギャップ)
bg-gray-200bg-{color}-{shade} にマッチbg-surface(サーフェス背景)
text-gray-500text-{color}-{shade} にマッチtext-muted(二次的なテキスト)
bg-blue-600bg-{color}-{shade} にマッチbg-accent(プライマリカラー)

修正前後の比較

生の Tailwind を使ったカードコンポーネントと、セマンティックトークンを使った同じコンポーネントを比較します。

修正前 — 生の数値ユーティリティ

// ArticleCard.tsx — design-token-lint に検出される
export function ArticleCard({ title, excerpt, tag }: Props) {
  return (
    <div className="p-4 bg-gray-100 rounded-lg">
      <h2 className="text-lg font-bold text-gray-900 mb-2">{title}</h2>
      <p className="text-sm text-gray-600 mb-4">{excerpt}</p>
      <div className="flex gap-2">
        <span className="px-3 py-1 bg-blue-600 text-white text-xs rounded">
          {tag}
        </span>
      </div>
    </div>
  )
}

リンターが検出するクラス: p-4bg-gray-100text-gray-900mb-2text-gray-600mb-4gap-2px-3py-1bg-blue-600

修正後 — セマンティックトークン

// ArticleCard.tsx — クリーン
export function ArticleCard({ title, excerpt, tag }: Props) {
  return (
    <div className="p-hsp-lg bg-surface rounded-lg">
      <h2 className="text-subheading font-bold text-fg mb-vsp-2xs">{title}</h2>
      <p className="text-small text-muted mb-vsp-xs">{excerpt}</p>
      <div className="flex gap-hsp-sm">
        <span className="px-hsp-sm py-vsp-2xs bg-accent text-bg text-caption rounded">
          {tag}
        </span>
      </div>
    </div>
  )
}

セマンティックバージョンが優れている理由

一元的な変更 — デザインチームがアクセントカラーを変更する場合、1つのCSS変数(--color-accent)を更新するだけで、コードベース全体のすべての bg-accent が更新されます。bg-blue-600 の場合は、何百ものファイルをgrepして置換する必要があります。

意図が伝わるbg-surface は「これはサーフェスレベルの背景」であることを伝えます。bg-gray-100 は数値以外何も伝えません。6ヶ月後も、トークン名は意味を持ち続けます。

ズレが起きないp-hsp-lg はどこでも同じ値です。p-4 は、誰かが Tailwind の設定を変更しない限り16pxですが、セマンティックトークンはテーマの上書きに耐えられます。

強制力がある — リンターはスタイルガイドの脚注をCIの失敗に変えます。違反はデザインレビューではなく、PRの段階で検出されます。

実際的な設定例

タイトトークンプロジェクト(推奨)

Tailwind のデフォルトをセマンティックトークンで完全に置き換えたプロジェクト:

{
  "prohibited": [
    "p-{n}", "px-{n}", "py-{n}", "pt-{n}", "pr-{n}", "pb-{n}", "pl-{n}",
    "m-{n}", "mx-{n}", "my-{n}", "mt-{n}", "mr-{n}", "mb-{n}", "ml-{n}",
    "gap-{n}", "gap-x-{n}", "gap-y-{n}",
    "space-x-{n}", "space-y-{n}",
    "bg-{color}-{shade}",
    "text-{color}-{shade}",
    "border-{color}-{shade}",
    "ring-{color}-{shade}"
  ],
  "allowed": ["p-0", "m-0", "gap-0", "p-px"],
  "ignore": [
    "**/*.test.*",
    "**/*.stories.*",
    "**/vendor/**"
  ],
  "patterns": [
    "src/**/*.{tsx,jsx,astro}",
    "components/**/*.{tsx,jsx,astro}"
  ],
  "suggestionSuffix": "use semantic token (hsp-*/vsp-*) or arbitrary value"
}

段階的な移行

生の Tailwind を使った大規模なコードベースがある場合、まずカラーだけ始めて、後でスペーシングを追加します:

{
  "prohibited": [
    "bg-{color}-{shade}",
    "text-{color}-{shade}",
    "border-{color}-{shade}"
  ],
  "allowed": [],
  "patterns": ["src/**/*.{tsx,jsx}"],
  "suggestionSuffix": "use a semantic color token instead"
}

カラーがクリーンになったら、スペーシングパターンを prohibited に追加します。

Next.js プロジェクト

{
  "prohibited": [
    "p-{n}", "m-{n}", "gap-{n}",
    "bg-{color}-{shade}",
    "text-{color}-{shade}"
  ],
  "allowed": ["p-0", "m-0", "gap-0"],
  "ignore": [
    "**/*.test.*",
    "**/*.stories.*",
    ".next/**"
  ],
  "patterns": [
    "app/**/*.{tsx,jsx}",
    "components/**/*.{tsx,jsx}",
    "pages/**/*.{tsx,jsx}"
  ]
}

Astro プロジェクト

{
  "prohibited": [
    "p-{n}", "m-{n}", "gap-{n}",
    "bg-{color}-{shade}",
    "text-{color}-{shade}",
    "border-{color}-{shade}"
  ],
  "allowed": ["p-0", "m-0", "gap-0"],
  "ignore": [
    "**/*.test.*",
    "dist/**",
    ".astro/**"
  ],
  "patterns": [
    "src/**/*.{astro,tsx,jsx}"
  ]
}

複数パッケージの monorepo

モノレポのルートに設定を置き、すべてのパッケージをスキャンします:

{
  "prohibited": [
    "p-{n}", "m-{n}", "gap-{n}",
    "bg-{color}-{shade}",
    "text-{color}-{shade}"
  ],
  "allowed": ["p-0", "m-0", "gap-0"],
  "ignore": [
    "**/node_modules/**",
    "**/dist/**",
    "**/*.test.*"
  ],
  "patterns": [
    "packages/*/src/**/*.{tsx,jsx,astro}",
    "apps/*/src/**/*.{tsx,jsx,astro}"
  ]
}

CI 統合 — GitHub Actions

違反があれば CI を失敗させる:

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm design-token-lint

Pre-push フック

lefthook でプッシュ前に実行:

# lefthook.yml
pre-push:
  commands:
    design-token-lint:
      run: npx design-token-lint

カスタムスクリプト

プログラマティック API を使ってプロジェクト固有のロジックでリンターを実行:

// scripts/lint-tokens.mjs
import { loadConfig, compileConfig, setConfig, lintFile } from '@zudolab/design-token-lint';
import { glob } from 'glob';

const config = await loadConfig(process.cwd());
setConfig(compileConfig(config));

const files = await glob('src/**/*.{tsx,jsx}');
let totalViolations = 0;

for (const file of files) {
  const results = await lintFile(file);
  for (const r of results) {
    console.log(`${r.filePath}:${r.line}  ${r.className}  ${r.reason}`);
    totalViolations++;
  }
}

process.exit(totalViolations > 0 ? 1 : 0);

正当な例外の無視

生の Tailwind を要求するサードパーティコンポーネントを統合する場合:

{/* design-token-lint-ignore — ベンダーコンポーネントがリテラル p-4 を要求 */}
<VendorCalendar className="p-4 bg-gray-50" />

すべてのコメント形式については無視構文を参照してください。

Revision History