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
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
380
CSS Linter による Baseline サポートの仕組み
ryo_manba
1
420
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
Lessons from Spec-Driven Development
simas
PRO
0
240
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
210
dRuby over BLE
makicamel
2
400
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
630
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
960
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
430
The NotImplementedError Problem in Ruby
koic
1
1.1k
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
780
act1-costs.pdf
sumedhbala
0
150
Performance Engineering for Everyone
elenatanasoiu
0
250
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
270
これからAgentCoreを触る方へトレンドはGatewayです
har1101
6
460
Featured
See All Featured
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
250
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Become a Pro
speakerdeck
PRO
31
6k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
550
Designing for humans not robots
tammielis
254
26k
Utilizing Notion as your number one productivity tool
mfonobong
4
340
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
320
Building AI with AI
inesmontani
PRO
1
1.1k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
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