← レポート一覧
どんなツールか
型ではなくリンターで決める理由
リンターが読んでいるもの
6つのルール
何を許すかと、エラーの文面を決める
エージェントで試した結果
見えないものと限界
導入の手順
まとめ
1 / どんなツールか
どんなツールか
@shadcn/lint は、Tailwind v4 のデザインシステム向けのリンターです。ESLint と Oxlint のプラグインとして動きます。名前に shadcn とありますが、shadcn/ui を使っていないプロジェクトでも使えます 。
README では「agent-first」、つまり UI を書くコーディングエージェントのために作ったと説明されています。
1 エージェントが UI を書く
<Button className="p-4 ">
2 lint を実行する
npm run lint
3 エラーに直し方が書いてある
p-4 は Button では使えない。
size(sm, lg)を使う。
4 デザインシステムの中で直す
<Button size="lg ">
実際のエラーは次のような文面です(README の画像より)。何がだめかに加えて、代わりに何を使えばよいか、どのファイルを見ればよいかまで書かれています。
"p-4" is not allowed on <Button>: <Button> owns its spacing.
Use a variant (sm, lg) for size. Use margin for layout or a gap on the
parent for space around it. Add a size in components/ui/button.tsx
only if the design explicitly calls for one.
決まりを守らせるだけでなく、あなたのコンポーネント・バリアント・テーマから直し方を提案する のが特徴です。
2 / なぜリンターか
型ではなくリンターで決める理由
「Button の margin と width は外から変えてよいが、padding は Button が決める」という決まりを考えます。TypeScript でも、style の型を Pick<CSSProperties, "margin" | "width"> に絞れば守らせることはできます。
TypeScript の場合
だめなことだけがわかる
TS2353: ... 'padding' does not exist
in type 'Pick<CSSProperties,
"margin" | "width">'.
padding が使えないことはわかりますが、Button の大きさをどう変えればよいか は書かれていません。
@shadcn/lint の場合
代わりに使うものまでわかる
"p-4" is not allowed on <Button>:
<Button> owns its spacing.
Use a size (sm, lg), or margin here
or gap on the parent ...
size の候補、margin や親の gap という別の置き場所、コンポーネントのファイルまで示します。
もう1つの理由は、リンターの設定はコンポーネントのコードを変えずに 書けることです。複雑な型を作る必要もありません。
同じ部品、別の決まり
プロジェクトごとにルールを変えられる
コンポーネントのコードはそのままで、プロジェクトごとに許す範囲を決められます。
自分のものでない部品
外部パッケージにもかけられる
サードパーティのコンポーネントにも、フォークやラッパーなしでルールを当てられます。
共通の設定
複数プロジェクトで共有できる
デザインシステムの共通設定を置き、各プロジェクトが自分のルールを足せます。
3 / しくみ
リンターが読んでいるもの
リンターはアプリを動かさず、ソースコードだけを読みます。そのとき、次の4つを材料にしてエラーと提案を作ります。
コンポーネント
import をたどって探す
テーマのトークン
@theme の --color-* など
Tailwind v4 本体
CSS が作られるクラスか
バリアント
cva・tv・props の型
あなたの .tsx
className・style など
@shadcn/lint
アプリは動かさない
どのクラスがだめで、代わりに何を使うか
コンポーネント
components.json が指す UI ディレクトリから探し、@/、tsconfig の paths、re-export や別名の import もたどります。ファイルがなければ components/ui などを見ます。
テーマのトークン
@theme の --color-* を読みます。bg-brand は宣言済みなので通り、bg-zinc-100(Tailwind の既定パレット)や未宣言の bg-highlight は指摘されます。色は OKLab で比べて近いトークンを提案します。
Tailwind v4 本体
インストール済みの Tailwind に、そのクラスで CSS が生成されるかを問い合わせます。hovr:flex のような打ち間違いがわかります。
バリアント
cva や tv の定義と、size?: "sm" | "lg" のような文字列ユニオンの props を読み、提案に使います。
クラスは className だけでなく、cn・clsx・cva などの呼び出し、同じファイル内の変数(1段まで)、className を受け渡すラッパーコンポーネントもたどって調べます。
4 / ルール
6つのルール
本番のデザインシステムを調べ、コーディングエージェントで試しながら作られたルールです。それぞれ warn か error で有効にします。
no-restyle
デザインシステムのコンポーネントに className で見た目を上書きすること。指摘 <Button className="p-4 bg-pink-500">
no-raw-colors
テーマにない色。Tailwind のパレット色、未宣言のトークン、SVG の fill="#ec4899" など。指摘 bg-pink-500 通る bg-primary
no-arbitrary-values
任意の値。同じ値がスケールにあればそれを提案します。指摘 p-[13px] → p-3.25 を提案
no-inline-styles
インラインの style と <style> 要素。動的な値は CSS のカスタムプロパティで渡します。
no-unknown-classes
Tailwind が CSS を作れないクラス。指摘 rounded-huge flex-cols
require-static-classes
リンターが読めないクラスの値。読めないと他のルールで調べられないためです。指摘 className={props.tone}
アプリのページ側
app/ や features/ など
no-restyle
no-arbitrary-values
require-static-classes
コンポーネントの中
components/ui/ など。自分で見た目を決める
no-restyle: off
no-arbitrary-values: off
require-static-classes: off
no-raw-colors と no-inline-styles は有効のまま
ドキュメントの推奨設定。コンポーネント自身は padding や ring-[3px] のような構造上の値を使うことがあるため
5 / 設定
何を許すかと、エラーの文面を決める
no-restyle は、クラスを色・文字・余白などのカテゴリ に分けて判定します。基本の設定 allow: ["layout"] では、配置(margin や width)だけを外から変えてよいことになります。
コンポーネントごとに別の決まりを持たせたいときは contracts を使います。次の図は、カードのタイトルには文字の調整を、カードの中身には余白の調整を許した例です。
配置 色 文字 余白
形 効果 動き
mt-4 bg-* text-sm p-4
rounded shadow animate
既定
allow: layout
CardTitle
+ typography
CardContent
+ spacing
塗りの丸が許可、白抜きの丸は指摘される。README の contracts の例を表にしたもの
// eslint.config.mjs または .oxlintrc.json の rules
"shadcn/no-restyle": ["error", {
allow: ["layout"],
contracts: [
{ pattern: "^CardTitle$", allow: ["layout", "typography"], deny: ["font-*"] },
{ pattern: "^CardContent$", allow: ["layout", "spacing"] },
],
}]
contract は書いたキーだけを置き換え、残りは上の設定を引き継ぎます。allow を書くときは、配置を残したければ layout も並べる必要があります。
エラーの文面も差し替えられます。{{sizes}} や {{variants}}、{{file}} などのプレースホルダーには、実際のコンポーネントの値が入ります。
設定
message: {
spacing: "Use a {{component}}
size: {{sizes}}.",
}
Button に sm と lg があるときの表示
Use a Button size: sm, lg.
settings.shadcn.note を使うと、すべてのルールのエラーの末尾に「DESIGN.md を見て」のような一文を足せます。
6 / 効果
エージェントで試した結果
作者たちは 150 回を超えるタスク実行で試しています。README の表は、モデルごとに1回分の実行で、lint のフィードバックを受ける前と後のエラー数を比べたものです。
フィードバック前のエラー数
後
Sonnet 5 Haiku 4.5 Opus 5
GPT 5.6 Terra GPT 5.6 Sol
8/8 8/8 8/8
8/8 6/8
69 66 42
117 98
0 0 0 0 0
README の表を図にしたもの。モデル名の横の数字は完了したタスク数(8 タスク中)
ルールの説明文だけを渡して自分で見直させる場合と、lint のエラーを渡す場合も比べています。Claude の3モデルでは、エラーを渡したほうが修正のコストが 10〜48% 低く、全タスクが1回の修正で通りました。
モデル ルール文のみ エラーを渡す 修正コストの差
Sonnet 5 $3.57 $2.47 約31%減
Haiku 4.5 $1.41(7/8 通過) $0.74(8/8) 約48%減
Opus 5 $4.35 $3.93 約10%減
差が大きいのは小さいモデルです。Haiku はルール文だけだと、間違ったトークン名を3回見直しても見つけられませんでしたが、そのクラスを名指しするエラーを渡すと1回で直しました。
修正前
ピンクのボタンを直接塗る
<Button className="bg-[#FF6B35]
text-white hover:bg-[#FF6B35]/90">
lint を受けた修正後
トークンとバリアントを足す
<Button variant="brand">
brand トークンを宣言し、Button に brand バリアントを追加する形に落ち着くことが多かったそうです。eslint-disable やインラインスタイルでごまかした実行はありませんでした。
別のモデル(judge)で修正前後の見た目を比べると、ほぼすべての組が 10 点満点中 9〜10 点でした。見た目を捨ててエラーを消したわけではない、という確認です。
ただし、これは特定の実行を測った結果 で、どのモデル・どのプロジェクトでも同じになる約束ではない、とドキュメント自身が書いています。evals の詳しい結果は Claude の3モデルが中心で、判定役のモデルも同じ系統です。
7 / 限界
見えないものと限界
lint が通っても、すべてのスタイルの経路を調べたことにはなりません。ドキュメントには、見えないものが具体的に挙げられています。
親のセレクタ
[&_button]:bg-primary
親から子のコンポーネントを塗る書き方は、no-restyle では追いません。
読めない props
<Button {...props}>
中身が読めないオブジェクトの展開は、そのままにされます。
import した値
ファイルをまたぐ値
クラスの値は同じファイル内だけをたどります。
素の CSS
globals.css や @apply
スタイルシートの中は対象外です。CSS 用のリンターを使います。
新しいトークン
@theme への追加
新しく宣言したトークンは、仕様として許されます。
無効化コメント
eslint-disable
コメントでルールを外せます。理由の記載を必須にする設定を勧めています。
わざとすり抜けを試す7つの実験では、5つを検出し、2つがすり抜けました。
検出した
5つ CSS カスタムプロパティ経由の生の16進色 / 変数に入れた動的なクラス文字列 / SVG の fill / ui コンポーネントの別名での再 export / これらの組み合わせ
すり抜けた
2つ globals.css に書いた素の CSS クラス / 新しいテーマトークンを作ること
新しいトークン・バリアント・contract・無効化コメントは、人がデザインの判断としてレビューする ものです。lint は、新しい見た目がシステムに入るべきかまでは決められません。
8 / 導入
導入の手順
必要なもの: Node.js 20.19 以上、Tailwind v4、ESLint 9.30 以上または Oxlint 1.80 以上(Oxlint の JS プラグイン API はまだ alpha)。npm の @shadcn/lint は 2026-09-15 時点で 0.1.0、ライセンスは MIT です。
1
エージェントにセットアップを頼む
README の Quickstart は、エージェントに次の文を渡す方法です。SETUP.md の指示では、プラグインの導入と登録だけを行い、ルールは有効にしません 。どのルールで何を許すかは利用者が決めます。
Read https://github.com/shadcn-ui/lint/blob/main/SETUP.md
and set up @shadcn/lint in this project.
2
自分で入れる場合は、まず no-restyle から
npm install -D @shadcn/lint oxlint
// .oxlintrc.json
{
"jsPlugins": ["@shadcn/lint"],
"rules": {
"shadcn/no-restyle": ["error", { "allow": ["layout"] }]
}
}
3
AGENTS.md に1行足す
lint のコマンドを package.json の lint スクリプトにしたうえで、エージェントへの指示に書きます。
After making changes, run `npm run lint` and fix all errors.
4
既存のプロジェクトは warn から少しずつ
最初は warn にして、多いパターン(ボタンの padding など)から直します。警告の数を --max-warnings 287 のように CI で上限にし、直すたびに下げます。新しいコードのフォルダだけ先に error にすることもできます。ルールがきれいになったら error に上げ、他のルールを足していきます。
9 / まとめ
まとめ
1
デザインシステムの決まりを、エージェントが自分で確かめられる形にするリンター
Tailwind v4 のプロジェクト向け。ESLint と Oxlint で動き、shadcn/ui は必須ではありません。
2
エラーに、プロジェクト自身のバリアント・サイズ・トークンを使った直し方が入る
contracts でコンポーネントごとに許す範囲を決め、メッセージも自分たちの言葉にできます。
3
試した実行ではエラーがほぼ1回でゼロになったが、見えない経路もある
素の CSS や新しいトークンは対象外です。新しいトークンやバリアントは人がレビューします。
出典と用語メモ
shadcn-ui/lint(GitHub、MIT、2026-09-02 作成) の README.md(冒頭画像を含む)、SETUP.md、docs/how-it-works.md、docs/rules.md、docs/rules/*.md、docs/design-systems.md、docs/adoption.md、docs/evals.md を 2026-09-15 に取得して使いました。
npm のバージョン(0.1.0)は npm view @shadcn/lint の結果です。第6章のコスト差の割合(約31%・48%・10%)は、docs/evals.md の表の金額から計算しました。
用語メモ / デザインシステム =色・余白・部品の使い方などの決まりと、それを形にしたコンポーネントの集まり。shadcn/ui =コードを自分のプロジェクトにコピーして使う形の React コンポーネント集。トークン =色や角丸などに名前を付けた値(例: --color-primary)。バリアント =コンポーネントに用意された見た目の種類(例: variant="destructive")。cva・tv =バリアントを定義するためのライブラリ(class-variance-authority、tailwind-variants)。Oxlint =Rust 製の高速な JavaScript/TypeScript リンター。
← レポート一覧