Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
CSS Linter の現在地 2025年のベストプラクティスを探る
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
mattsuu
September 20, 2025
Programming
4.4k
12
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
CSS Linter の現在地 2025年のベストプラクティスを探る
フロントエンドカンファレンス東京 2025 (2025/09/21) での発表資料
https://fec-tokyo.connpass.com/event/352581/
mattsuu
September 20, 2025
More Decks by mattsuu
See All by mattsuu
pnpm に provenance のダウングレード を検出する PR を出してみた
ryo_manba
1
370
CSS Linter による Baseline サポートの仕組み
ryo_manba
1
410
React Aria で実現する次世代のアクセシビリティ
ryo_manba
5
3.2k
5分で分かる React Aria の 良いところ・これからなところ
ryo_manba
5
7k
アクセシブルなインクリメンタルサーチを作ってみた
ryo_manba
2
620
Next.js の fetch 拡張とキャッシュ機構の違いを理解する
ryo_manba
6
1.9k
React Spectrum Libraries によるアクセシブルなUIの構築
ryo_manba
0
4.6k
Other Decks in Programming
See All in Programming
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
20
6.4k
TAKTでAI駆動開発の品質を設計する
j5ik2o
6
1.1k
Modding RubyKaigi for Myself
yui_knk
0
910
AIで効率化できた業務・日常
ochtum
0
120
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
250
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
320
AIとRubyの静的型付け
ukin0k0
0
560
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
480
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
13
3.6k
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
3.6k
AIとASP.NET Coreで雑Webアプリを作った話
mayuki
0
490
Copilot CLI の継戦能力を高める コンテキスト管理
nozomutu
1
1.2k
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
A Tale of Four Properties
chriscoyier
163
24k
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
Utilizing Notion as your number one productivity tool
mfonobong
4
320
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
720
A Modern Web Designer's Workflow
chriscoyier
698
190k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
Writing Fast Ruby
sferik
630
63k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1.1k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
Transcript
CSS Linter の現在地 2025 年のベストプラクティスを探る まっつー / @ryo_manba 2025/09/21 フロントエンドカンファレンス東京
2025
自己紹介 まっつー メルカリ フロントエンドエンジニア Stylelint チームメンバー 𝕏: @ryo_manba GitHub: @ryo-manba
2
Quiz: この CSS どう思いますか? .test-1 { colro: red; padding: 16pp;
} .test-2 { background-color: red; background: url("images/bg.gif") no-repeat left top; } .test-3 { field-sizing: content; } 3
Linter にかけてみる 4
正当性の問題 CSS の仕様的に「間違っているもの」を検出 colro → 未知のプロパティ 16pp → 未知の単位 .test-1
{ colro: red; padding: 16pp; } 5
保守性の問題 仕様的に問題ないが、バグの温床になるパターンを検出 ショートハンドの上書き → background-color: red が無効に .test-2 { background-color:
red; background: url("images/bg.gif") no-repeat left top; /* background-color は transparent にリセットされる */ } 6
互換性の問題 サポート対象のブラウザで使えない機能を検出 field-sizing: content; → 現状は Chromium 系のみ。Safari / Firefox
は未実装 .test-3 { field-sizing: content; } 7
CSS Linter がやること 正当性 タイポ/ 構文/ 値の誤りの検出 保守性 変更に弱い書き方の検出(上書き/ 重複/
不要指定など) 互換性 サポート対象に基づく利用可否の検出 8
This talk 2025 年時点の Stylelint / Biome / ESLint を横断比較し、
最適な CSS Linter の“ 選定と運用” の勘所を解説します。 9
目次 ツールの現在地と推奨構成(Stylelint / Biome / ESLint ) カスタマイズのしやすさ Tailwind ・CSS
Modules ・CSS-in-JS ・SCSS 互換性の基準(Baseline / browserslist ) MCP との組み合わせ 段階的な導入 10
ツールの現在地と推奨構成 11
Stylelint: 特徴 100 を超える builtin ルール + 豊富なエコシステム 誤検知を極力避ける設計 →
警告が出たら迷わずに直せる Plugin に委ねる領域 文脈依存/ 助言的(a11y 、互換性、フォーマット) 12
Stylelint: 推奨構成 recommended = 壊さない最小セット CSS 仕様的な不正(無効値・未知な構文 など)を検出 standard =recommended
+ モダンな表記の統一 例:ベンダープレフィックス禁止 / kebab-case 命名 / range の context 記法 ( @media (width >= 768px) ) // 壊さない土台 { "extends": ["stylelint-config-recommended"] } // 規約まで含める { "extends": ["stylelint-config-standard"] } 13
ESLint: 特徴 2025/02 に CSS を公式サポート(@eslint/css ) 10 を超える built
in ルール プロパティ/ At-rule の妥当性 ( no-invalid-* 系) Baseline 準拠 ( use-baseline ) Cascade Layers など最新CSS 潮流にも対応 14
ESLint: 推奨構成 css/recommended で 正当性+互換性の土台を一括で有効化 import { defineConfig } from
"eslint/config"; import css from "@eslint/css"; export default defineConfig([ { files: ["**/*.css"], language: "css/css", plugins: { css }, extends: ["css/recommended"], }, ]); 15
Biome: 特徴 Lint / Format を同梱(1 ツールで整形+検証) まずは 壊さない土台優先 Stylelint
の recommended のルールを実装 自前パーサ+データを一元管理 tokenizer / parser / プロパティ・At-rule 定義を内包 → 依存が少 ない 16
Biome: 推奨構成 Recommended 相当は linter.enabled をオンにすれば適用 { "css": { "linter":
{ "enabled": true } } } 17
3 つのツール比較 Stylelint Biome ESLint リリース 2015 年 2023 年
2025 年2 月 言語 JavaScript Rust JavaScript ルール数 100+ 20+ 10+ 特徴 CSS 専門 豊富なエコシステム 高速 Formatter 同梱 一元管理 シンプルな設定 一元管理 18
カスタマイズのしやすさ 19
二種類のカスタマイズ built-in ルールの柔軟性 誤検知が出た箇所のピンポイントな無効化 固有の Property / At-rule の許容など、細かく調整できるか 独自ルールの追加しやすさ
20
ルール設計から見るカスタマイズ性の違い 21
At-rule の validation 、これだけで大丈夫? @charset "UTF-8"; /* OK */ @foo;
/* NG ? */ 未知の At-rule を弾くだけで十分に見えるが、それだけでは不十分 22
実際に必要な「4 つの正当性」 at-rule 名 → @foo のような未知 at-rule を禁止 prelude
→ @property --x {} の --x が妥当か descriptor → @counter-style foo { bar: red; } の bar が妥当か descriptor value → @counter-style foo { system: baz; } の baz が妥当か At-rule の妥当性チェックは この4 軸 が必要。 23
At-rule 検証の設計差 Stylelint: 4 つの個別ルールに分割して検出 ルールごとに secondary options (例: ignoreAtRules
)でピン ポイント除外 ESLint: 1 つのルールでまとめて検出 言語定義( languageOptions.customSyntax )拡張で誤検知を減ら す Biome: at-rule 名の validation のみ オプションなし 24
Stylelint の「細粒度」なルール設計 { "rules": { // 1) 未知の at-rule を禁止(Tailwind
等は個別に逃がす) "at-rule-no-unknown": [true, { "ignoreAtRules": ["tailwind", "apply", "screen", "theme", "layer"] }], // 2) at-rule の不正な prelude を禁止(必要なら特定 at-rule を除外) "at-rule-prelude-no-invalid": [true, { "ignoreAtRules": ["property"] }], // 3) at-rule 内の未知 descriptor を禁止 "at-rule-descriptor-no-unknown": true, // 4) at-rule 内の未知の descriptor の値を禁止 "at-rule-descriptor-value-no-unknown": true } } 25
ESLint: 言語定義で“ 許容範囲” を広げる export default defineConfig([ { //... languageOptions:
{ customSyntax: { // @my-at-rule "hello world!"; が正しい at-rule として認識される atrules: { "my-at-rule": { prelude: "<string>", }, }, }, }, }, ]); 26
ルール設計のまとめ ツール 基本方針 オプション 誤検知への対処 Stylelint 検出軸を細分化 (ルール分割) あり (
ignoreAtRules 等) オプション /* stylelint-disable */ ESLint/css 1 ルールで包括 少なめ 言語定義拡張で対応 customSyntax で拡張 /* eslint-disable */ Biome Stylelint の recommended 相当を 実装中 未実装 /* biome-ignore */ 27
カスタムルールの追加のしやすさ 28
柔軟に独自ルールを足せる価値 組織のナレッジをルール化して再現性を高める レビュー負荷を定常的に削減 コーディングガイドだと抜け漏れが生まれる 29
Stylelint :プラグインで実装 特徴:PostCSS AST を直接扱える export default stylelint.createPlugin("plugin/no-foo", () =>
{ return (root, result) => { root.walkRules((rule) => { if (rule.selector.includes("foo")) { stylelint.utils.report({ result, ruleName: "plugin/no-foo", message: ' セレクタに "foo" は使用禁止', node: rule, }); } }); }; }); 30
ESLint :カスタムルールで実装 特徴:ESLint の作法で書ける create(context) { return { Rule(node) {
const selector = context.sourceCode.getText(node.prelude); if (selector.includes("foo")) { context.report({ node: node.prelude, message: ' セレクタに "foo" は使用禁止', }); } }, }; } 31
ESLint :簡易的に独自ルールを作成 no-restricted-syntax で簡単に追加できる { rules: { "no-restricted-syntax": ["error", {
selector: "Declaration[important=true]", message: "!important は使用禁止" }] } } 32
Biome :GritQL でルール記述 language css; `$selector { $props }` where
{ $selector <: r".*foo.*", register_diagnostic( span = $selector, message = " セレクタに 'foo' は使用禁止" ) } 注意: GritQL はまだ alpha 版:v0.1.0-alpha.1743007075 最終リリース:2025/03/27 33
カスタムルール開発の比較 項目 Stylelint ESLint Biome 学習コ スト 中(PostCSS AST )
低〜中(ESLint 流儀) 高(DSL 学習が必要) 開発体 験 CSS 専門API が 充実 JS 開発者に馴染 む パターンマッチ / 直 感的な記述 34
互換性の基準(Baseline / browserslist ) 35
State of CSS 2025 から見える“ 互換性” の課題 Browser support が多くのカテゴリでペインポイントの上位に
Interactions: 1 位(16%) Other: 1 位(19%) Typography: 2 位(13%) Layout: 3 位(9%) Shapes & Graphics: 3 位(9%) Colors: 3 位(7%) ref: https://2025.stateofcss.com/en-US/features 36
互換性チェックは 2 つのアプローチ browserslist 基準:サポートブラウザに合わせる Baseline 基準:Web 全体で安定して使える時期で揃える 37
browserslist で守る(Stylelint ) プラグイン: stylelint-no-unsupported-browser-features を使用 目標ブラウザに未対応な機能を警告 { "plugins": ["stylelint-no-unsupported-browser-features"],
"rules": { "plugin/no-unsupported-browser-features": [ true, { "browsers": ["last 1 chrome version"], } ] } } 38
Baseline というもう一つの基準 Web 標準の成熟度を 3 段階で表す Limited / Newly available
/ Widely available (30 ヶ月以上) 対応ブラウザ一覧ではなく、広く安全に使えるしきい値で判断 ツール 実装状況 ESLint Built-in Stylelint Plugin Biome 未実装 39
Baseline の設定例 builtin ルールで提供されている { rules: { // widely /
newly / 年指定(例: 2024 ) "css/use-baseline": ["warn", { available: "widely" }] } } 40
Stylelint: Baseline の設定例 (plugin) ESLint と同じ I/F で設定可能 { plugins:
["stylelint-plugin-use-baseline"], rules: { "plugin/use-baseline": [true, { available: "widely" }] }, } 41
まとめ(互換性) まず管理軸を決める 既に browserslist を運用 → Stylelint + no-unsupported-browser- features
機能の安定性ベース → Baseline (ESLint or Stylelint) 42
Tailwind ・CSS Modules ・CSS-in-JS ・SCSS 43
Tailwind CSS おさらい <!-- 従来のCSS: クラスに意味を持たせる --> <button class="btn btn-primary">
送信</button> <!-- Tailwind CSS: ユーティリティクラスを組み合わせる --> <button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> 送信 </button> 小さな単機能のユーティリティクラスを組み合わせる p-4 = padding 、 bg-red-500 = 背景色、 text-xl = 文字サイズ 44
クラス文字列は CSS Linter の対象外 // JSX の中のクラス文字列 <div className="p-2 p-3
bg-red-500" /> 通常の CSS Linter は CSS の構文(宣言, at-rule など) を解析。 ただの文字列であるクラス( p-2 p-3 )は、そのままでは対象外。 /* CSS Linter が期待する形式 */ .button { padding: 0.5rem; /* ← property: value の宣言 */ background-color: red; } 45
eslint-plugin-tailwindcss の登場 発想の転換:CSS 宣言ではなく「クラス名の語彙」を検査 // 検出できる問題 <div className="p-2 p-3" />
// padding の競合 <div className="text-foo my-custom" /> // 存在しないクラス名 // 自動修正も可能 <div className="mx-5 my-5" /> // → m-5 ( 短縮形) <div className="pt-2 pb-4" /> // → pb-4 pt-2 ( 推奨順序にソート) JSX, Vue, HTML... 幅広くサポート Tailwind CSS v4 対応中 (beta で公開) 46
従来の CSS Linter と TailwindCSS 47
Stylelint × Tailwind CSS stylelint-config-tailwindcss で誤検知を回避 @tailwind , @apply ,
theme() など Tailwind 構文でエラーを出さない 通常の CSS を書く領域 (Custom CSS, Global CSS) に適用し品質担保 export default { extends: ["stylelint-config-tailwindcss"] }; 48
ESLint + tailwind-csstree languageOptions.customSyntax で Tailwind 構文を追加し、 @eslint/css が 未知扱いせず構文検証が可能に
import { tailwind4 } from "tailwind-csstree"; export default defineConfig([ { language: "css/css", plugins: { css }, languageOptions: { customSyntax: tailwind4 } }, ]); 49
Tailwind 構文を適切に読み込む @tailwind base; /* 正しい値 */ @tailwind foo; /*
不正な値を検出 */ @apply text-white bg-blue-500; /* 正しい構文 */ @apply { color: red; } /* 無効な @apply 構文 */ a { background: theme(colors.blue.500); /* 正しい関数呼び出し */ color: theme(fake.value); /* 不正なキー */ } 50
Biome × Tailwind CSS ルール: useSortedClasses (クラスの並び順を整える) 開発中のため部分的な実装 - <div
class="px-2 foo p-4 bar" />; + <div class="foo bar p-4 px-2" />; 51
Tailwind CSS サポートまとめ ツール クラス文字列 Tailwind 構文 eslint-plugin-tailwindcss ◎ 専門
× ESLint + tailwind-csstree × ◎ 構文解釈 Stylelint × △ 誤検知の抑制 Biome △ ソート × クラス文字列の検査 と CSS 構文の検査 を分けて設計するのが ◎ 52
CSS Modules の対応 拡張構文を使わないなら、どの Linter でも問題なし 独自構文( @value , :export
, composes など)を使う場合は、ツー ルごとに対応差あり 53
CSS Modules: サポート比較 機能 / 構文 Stylelint (+stylelint-config- css-modules )
Biome @value 対応 対応 composes: 対応 対応 compose-with: 対応 未対応 :local() 対応 未対応 :global() 対応 対応 :export 対応 未対応 ※ ESLint は全て未対応。 @value を使うとパースエラーになる。 54
CSS-in-JS の対応 Stylelint のみ可能 Biome, ESLint は未実装 import styled from
"styled-components"; /* CSS エラーを含む例 */ const ErrorButton = styled.button` foo: bar; /* 未知プロパティ */ color: baz; /* 不正な値 */ width: 100zz; /* 未知の単位 */ `; 55
Stylelint × CSS-in-JS customSyntax を指定して、JSX 内の CSS を lint 可能に
{ "customSyntax": "postcss-styled-syntax", } ❯ npx stylelint Button.jsx Button.jsx 5:3 Unexpected unknown property "foo" property-no-unknown 6:10 Unexpected unknown value "baz" for property "color" declaration-property-value-no-unknown 7:10 Unexpected unknown value "100zz" for property "width" declaration-property-value-no-unknown 7:13 Unexpected unknown unit "zz" unit-no-unknown 4 problems (4 errors, 0 warnings) 56
Less / Sass / SCSS 対応 ツール SCSS Less Sass
備考 Stylelint 共有設定 or customSyntax で対応 ESLint SCSS サポートの PR は出てるが止まっている Biome 57
MCP との組み合わせ 58
MCP サポート状況 ESLint :公式 MCP サーバーあり( eslint --mcp ) Stylelint
:コミュニティ製 stylelint-mcp /公式移管は議論中 Biome :MCP サーバーは未提供(RFC あり) 。 JS API Bindings (alpha ) で自作は可能 59
MCP を使うべき理由 AI Agent に 「lint して直して」と依頼しても ルール誤認・見落とし が発生することも プロンプト往復が減る:
LLM→Tool→結果、で無駄な“ 推測” を排除 Linter を実行させると時間がかかる 60
MCP により精度と再現性が向上 61
段階的な導入 62
段階的な導入(小さく始めて広げる) 1. Linter の導入 2. ルールを 1 つ追加 3. 差分を修正(or
抑制) 4. 次のルールへ(2 に戻る) 63
現実の壁 レガシーコードは修正の影響範囲が不明 Global CSS は特にリスクが高い 一つのエラー修正にも膨大な確認コスト → 導入が進まない 64
Bulk Suppressions ESLint v9.24 の新機能 既存の違反を記録し、新規だけ検出する 65
実例: 初回実行 /* style.css */ a {} /* 空のブロック */
a { foo: red; /* 未知のプロパティ */ } $ npx eslint style.css style.css 2:3 error Unexpected empty block found css/no-empty-blocks 5:3 error Unknown property 'foo' found css/no-invalid-properties 2 problems (2 errors, 0 warnings) 66
実例: Suppressions 生成 # 既存違反を一括抑制 $ npx eslint . --suppress-all
// 生成される `eslint-suppressions.json` { "style.css": { "css/no-empty-blocks": { "count": 1 }, "css/no-invalid-properties": { "count": 1 } } } ファイル× ルール× 件数のカウントが json に記録される 67
実例: Suppressions が適用される 再実行すると... $ npx eslint style.css # エラーなし!
68
実例: 新規違反は検出 a {} /* 既存: 抑制済み */ a {
foo: red; /* 既存: 抑制済み */ foo: red; /* ⬅︎ 新規追加 */ } $ npx eslint style.css style.css 5:3 error Unknown property 'foo' found css/no-invalid-properties 6:3 error Unknown property 'foo' found css/no-invalid-properties 2 problems (2 errors, 0 warnings) 69
Bulk suppressions を利用した導入 1. 現在のエラー → JSON に保存 2. 新規コードは厳格にチェック
3. 既存エラーは自分のペースで修正 → 今すぐルールを有効化できる 70
注意点: IDE ではエラーが表示される IDE 連携は未対応(VS Code 等では既存違反が見え続ける) CI では落ちないが、実装時のノイズに 対応状況:
RFC 提案中 将来は抑制済みを別の色やヒントとして表示する案あり 71
各ツールの Bulk suppressions の対応状況 ツール 状況 備考 ESLint 利用可能 v9.24+
/ IDE 未対応 Stylelint 実装中 PR #8564 Biome 未対応 (JS 系のコメント挿入は別機能) 72
Suppressions なしで段階導入 A. overrides で新規だけ厳しく // eslint.config.js export default [{
files: ["src/new/**/*.css"], rules: { "css/no-invalid-properties": "error" } }]; B. コメントで局所的に回避 /* stylelint-disable no-empty-blocks */ a {} /* stylelint-enable */ 73
まとめ 74
まとめ Stylelint 規約系・細粒度な調整・SCSS/Less ・CSS-in-JS まで面倒見が良い ESLint (@eslint/css ) 妥当性+Baseline をまず担保。単一
ESLint 運用にフィット。 Biome Lint/Format 一体・高速。まずは “ 壊さない土台” から始めたいとき に軽快(機能は拡充中) 。 75