やってみた 2026年7月26日

AI生成UIの『っぽさ』を60ルールで機械検出するimpeccableを、自作のダサいHTMLで試したら仕込んでないa11y違反まで刺さった

XECIN JavaScriptフロントエンドデザイン静的解析

GitHubのトレンドを眺めていたら、pbakaus/impeccable というのが上のほうに来ていました。説明は「Design guidance for AI coding agents.」。スター数を見たら5万を超えていて、正直「デザインのガイドラインでそんなに伸びるんだ」と思ったのが最初の印象です。

僕はまだエンジニア2年目で、担当はフロント寄りです。最近チームでAIコーディングツールを使う場面が増えてきて、生成されたUIを見ると「なんか、それっぽいけど、どこかで見た感じ」がするんですよね。その正体をうまく言葉にできなくて、レビューでも「もう少し個性がほしい」みたいなふわっとした指摘しかできずにいました。

impeccableは、その「AIっぽさ」を 60個のルールで機械的に検出する と書いてありました。ふわっとした感覚が数字とルール名で出てくるなら、それは僕が一番ほしかったやつです。というわけで、使い捨てのコンテナに放り込んで動かしてみました。

試した環境

  • 使い捨ての node:22-bookworm コンテナ(ホストはWindows + Docker)
  • Node.js v22.23.1
  • pbakaus/impeccablemain、コミット d272b9b(CLIバージョンは 3.3.1、ライセンス Apache-2.0)

恥ずかしながら、最初は「AI向けのデザインスキルなら、動かすにもAIのAPIキーが要るんだろうな」と思い込んでいました。ところがREADMEをちゃんと読むと、このツールは二層構造になっていて、AIコーディングツールに入れて使う23個のスラッシュコマンド(/impeccable polish みたいなやつ)とは別に、キーもLLMも使わずに動く決定的な検出器のCLI が同梱されていました。今回試したのはこの後者だけです。

npm install は18秒で終わりました。依存を見たら css-treehtmlparser2 といったHTML/CSSをパースするライブラリだけで、AI関連のパッケージは入っていません。これは本当にローカルで完結するやつだと分かって、少し安心しました。

git clone --depth 1 https://github.com/pbakaus/impeccable
cd impeccable && npm install --no-audit --no-fund

# サブコマンドはこれだけ。今回使うのは detect
node cli/bin/cli.js --help
#   detect [file-or-dir-or-url...]  Scan for UI anti-patterns and design quality issues
#   ignores / install / link / update / check

わざとダサいHTMLを書いて食わせてみる

同梱のデモに対して回すだけだと「作った人が刺さるように仕込んだやつでしょ」と疑ってしまうので、自分で「いかにもAIが吐きそうなランディングページ」を手書きしました。フォントはInter、見出しは紫→青のグラデーション文字、暗い背景にカード、そのカードの中にまたカード、アイコンは角丸の56px四方……という、例のあれです。

そのHTMLをそのまま detect に渡すと、13件のアンチパターンが返ってきました。

/work/sample-landing.html
  [gradient-text] background-clip: text + gradient
  [low-contrast] 3.8:1 (need 4.5:1) — text #6b7280 on #12121f
  [low-contrast] 3.5:1 (need 4.5:1) — text #6b7280 on #1a1a2e
  [low-contrast] 3.5:1 (need 4.5:1) — text #ffffff on #ec4899
  [dark-glow] Zero-offset box-shadow glow (#8b5cf6)
  [hero-eyebrow-chip] eyebrow chip (tracked-caps) "AI-native platform" above h1 "Ship faster..."
  [overused-font] Primary font: inter
  [nested-cards] Card inside card (div)
  [skipped-heading] <h1> ... followed by <h3> "Lightning Fast" (missing h2)
  [ai-color-palette] Purple/violet accent colors detected
  ...
13 anti-patterns found.

これがけっこう刺さりました。低コントラストは「なんとなく読みにくいかも」ではなく、#6b7280#12121f の上に置いたら 3.8:1(AAは4.5:1が必要) と、コントラスト比が実数で出てきます。ふわっとしていたものが、ちゃんと数字になっている。

いちばん「おっ」となったのは skipped-heading でした。僕はデザインの「っぽさ」を見てもらうつもりで書いたので、h1 の次にいきなり h3 を置いていることに気づいていなかったんですよね。見出しレベルの飛ばしはスクリーンリーダーの目次を壊すというアクセシビリティの指摘で、これは狙って仕込んだものではありません。デザインのlintのつもりが、a11yの見落としまで拾ってくれたのは素直にありがたかったです。

--json を付けると、antipattern / severity / category / file / line / snippet を持ったオブジェクトの配列で返ってきます。CIやレビューbotに食わせるならこっち、という設計に見えました。

「60ルール」を鵜呑みにせず、自分で数えてみた

READMEには「60 deterministic detector rules」と書いてあります。ここは先輩に「数字は自分で裏を取れ」とよく言われるところなので、ルールを定義しているレジストリのファイルを開いて数えてみました。

grep -oE "id:\s*['\"][a-z0-9-]+['\"]" cli/engine/registry/antipatterns.mjs | sort -u | wc -l
# 60

grep -oE "category:\s*['\"][a-z-]+['\"]" cli/engine/registry/antipatterns.mjs | sort | uniq -c
#   27 category: 'quality'
#   33 category: 'slop'

ちゃんと60ありました。内訳は「品質寄り(quality)」が27、「AIっぽさ寄り(slop)」が33。低コントラストや見出し飛ばしのようなアクセシビリティ系はquality、グラデーション文字や紫パレットのような「見た目の量産感」はslop、という分け方になっているようです。数えてみて初めて、このツールが「a11yチェッカ」と「AIっぽさチェッカ」を1本にまとめたものなんだと腑に落ちました。

分類件数
quality(品質)27low-contrast / skipped-heading / tight-leading
slop(AIっぽさ)33gradient-text / overused-font / ai-color-palette

動かして初めて分かった、2つの「あれ?」

ひとつめ。同梱デモの index.html に対して回すと、僕の自作HTMLでは出なかった design-system-font-size というルールが「font-size: 14px is off the DESIGN.md type ramp」のように何件も出ました。最初は「同じHTMLっぽいのに何で?」と混乱したんですが、デモのフォルダには DESIGN.md(デザインシステムの定義)が置いてあって、そこに書かれた型(type ramp)から外れたサイズを叱るルールだったんですね。僕の単体HTMLには DESIGN.md が無いので、その手のルールはそもそも発火しない。つまり、このツールは「一般的なアンチパターン」だけでなく「あなたのプロジェクトのルール」にも照らしてくれる。ここは好みが分かれるところですが、社内の規約を DESIGN.md に書いておけば、それ専用のlinterになるということでもあります。

ふたつめは、完全に僕のミスです。終了コードを確かめようとして、こう書いてしまいました。

# やりがちなミス:tail の終了コードを拾ってしまう
node cli/bin/cli.js detect sample-landing.html 2>&1 | tail -8; echo $?
# → 0 と出てしまい「あれ、失敗してないの?」と一瞬勘違い

パイプの右側(tail)の終了コードを見てしまっていて、「13件も見つかってるのに0で正常終了してる?」と首をかしげました。先輩に聞いたら「それ、$? はパイプの最後のコマンドのだよ」と一言で終わりました……。リダイレクトで測り直したら、ちゃんと結果が変わりました。

node cli/bin/cli.js detect sample-landing.html >/dev/null 2>&1; echo $?
# 2  (検出あり)

# em-dash を10個ばらまいた文章だけのHTML
node cli/bin/cli.js detect copy.html >/dev/null 2>&1; echo $?
# 0  (advisory のみ)

このem-dashのやつが面白くて、(emダッシュ)を本文に10個入れたHTMLを食わせると、[em-dash-overuse] が「10 em-dashes in body text」と出るんですが、これは通常の検出とは別の「Advisory(失敗にはカウントしない)」という枠で表示されて、終了コードは0のままでした。しきい値は「本文500文字あたり1個くらいの密度で、8個以上」。人間だってemダッシュは普通に使うから、少しくらいでは怒らず、明らかに多用しているときだけそっと指摘する、という設計です。--no-advisory を付けると、この手の助言はまるごと消えます。CIで落としたいものと、参考までに見せたいものを、ちゃんと分けているんだなと思いました。

ついでに、CSSファイル単体(.css)を渡すと挙動が変わることにも気づきました。HTMLはDOMとして解析されるので低コントラストやカードの入れ子まで見てくれますが、CSS単体だと正規表現ベースの走査になって、拾えるのは overused-fontgradient-text くらい。行番号は付くものの、文字色と背景色の組み合わせのような「要素の関係」が要る指摘は出ません。ここは対象ファイルの種類で得意・不得意がある、と覚えておくとよさそうです。

試してみての所感

僕が一番使いたいのは、レビューに出す前の自分のHTMLを一回 detect に通して、gradient-textlow-contrast を潰しておく、という素朴な使い方です。キーが要らないのでコミット前フックにも入れやすいし、何より「なんとなくダサい」を「このルールに引っかかっている」と言い換えてくれるのが、経験の浅い僕にはありがたい。指摘に納得できないときは、ファイル内に impeccable-disable-line のコメントを書けばその場だけ黙らせられる仕組みもあったので、頭ごなしに従わされる感じもなさそうでした。

一方で、看板になっている23個のAIスラッシュコマンドや、ブラウザで見た目を反復調整する機能は、AIコーディングツール側に入れて使う前提なので、今回は動かしていません。そこは別途キーや環境が要るはずで、今回確かめたのは「キーなしで動く決定的な検出器」の部分だけ、というのは正直に書いておきます。

とはいえ、h1 の次に h3 を置いていた自分のうっかりを機械に指摘してもらえた時点で、僕としては十分もとが取れた気分です。しばらくは手元のブランチで回してみようと思います。もっと良い使い方があったら教えてください。