modern-css について
modern-cssは、エージェントや開発者向けにモダンCSSの一貫した記述ルールを定めるスキルです。Modern Web Guidanceを補い、stylelint-config-modernでは強制できない指針も扱います。
主な機能
- 01CSSレイヤーとスコープの規則
- 02ネストと範囲クエリの規約
- 03論理方向レイアウトと流動サイズ
- 04タイポグラフィと配色の指針
- 05モーション設定への対応
- 06CSSレビューとリファクタリング手順
活用シーン
- 01新しいコンポーネントのCSS作成
- 02既存スタイルの規則に沿った改修
- 03CSSのモダンな記述パターンの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moderncss/ skills --skill modern-cssインストール後、Claude Code で/modern-cssを入力して読み込まれたか確認します。
詳細
モダン CSS
エージェントと人が一貫した CSS を書けるようにする、モダン CSS の推奨ルールです。
これらのルールは、インストールが必須の Modern Web Guidance スキルを補完します。このスキルでは、各機能の動作や適用場面、フォールバック方法について説明しています。どの色空間を使うか、スコープを設定するか、いつネストするか、文字サイズをどう決めるかといった選択肢はあえて限定していません。これらのルールで選択肢を定め、それ以外についてはガイドを参照してください。両者の内容が異なる場合は、このルールが優先されます。
stylelint-config-modern パッケージは、リンターで適用可能なすべてのルールを適用します。このスキルは、エージェントが最初からそれらのルールに従ってコードを書けるようにし、リンターで適用できないルールも補います。
ブラウザーサポートポリシー:Baseline で Newly Available に分類される機能は、プログレッシブエンハンスメントとして、@supports フォールバックを使わずにネイティブで使用します。
ルール
アーキテクチャ
スタイルの整理(@layer)
- ルール:すべてのスタイルルールを
@layer内に記述します。 - 制約:レイヤーに属さない要素のデフォルトスタイルやリセットは避けます。
- 理由:レイヤーに属さないルールはすべてのレイヤーより優先されます。すべてをレイヤーに入れることで、レイヤーの順序だけで競合を解決できます。
- 参考資料:MDN の
@layer。 - 例:
@layer elements, components;
@layer elements {
a {
text-decoration-skip-ink: auto;
}
}
スタイルのカプセル化(@scope)
- ルール:各コンポーネントのスタイルには
@scopeを使います。コンポーネントが所有しないコンテンツ(埋め込みコンポーネント、スロットコンテンツ、ユーザーコンテンツなど)を含む場合は、制限を設定します(@scope (outer) to (inner))。 - 制約:スコープ外にあるコンポーネントのスタイルと、コンポーネントが所有しないコンテンツに対する無制限のスコープは避けます。
- 理由:スコープ付きセレクターは他のコンポーネントに漏れず、制限を設定することで、外側のスコープのスタイルが投影されたコンテンツに適用されるのを防ぎます。ドーナツスコープと呼ばれることもあります。
- 参考資料:MDN の
@scope。 - 例:
@scope (.card) to (.content) {
h2 {
font-size: var(--large);
}
}
ネストルールとアットルール(&)
- ルール:ルールとアットルールは親ルール内にネストします。ネストしたセレクターを親に結び付ける箇所では
&(&:hover)を記述し、コンビネーターでは省略します(& bではなくb)。 - 制約:ネストされていないセレクター(例:
a {} a:hover {})は避けます。 - 理由:ルールの状態やクエリーを同じブロック内にまとめられます。また、stylelint-config-modern によって、暗黙の
&形式が適用されます。 - 参考資料:MDN の
&ネストセレクター。 - 例:
a {
color: var(--blue);
&:hover {
text-decoration: underline;
}
code {
font-variant-numeric: tabular-nums;
}
}
範囲指定クエリー(20em < width <= 40em)
- ルール:サイズクエリーには範囲指定構文を使います(例:
@container (20em < width <= 40em))。範囲が重ならないようにします。 - 制約:
min-width/max-widthと、重複する範囲(例:20emでも両方に一致するwidth <= 20emとwidth >= 20emの併用)は避けます。 - 理由:各サイズが必ず1つの条件だけに該当するため、ある条件で別の条件を上書きする必要がありません。
- 参考資料:MDN のメディアクエリーの範囲指定構文。
- 例:
.card {
@container (width <= 20em) {
grid-template-columns: 1fr;
}
@container (20em < width <= 40em) {
grid-template-columns: 1fr 1fr;
}
@container (width > 40em) {
grid-template-columns: repeat(3, 1fr);
}
}
レイアウト
フロー相対レイアウト(*-inline-*、*-block-*、cqi/vi、start/end)
- ルール:レイアウトには、フロー相対プロパティ(例:
padding-block-start、inset-inline、inline-size)、単位(例:cqi、cqb、vi)、キーワード(例:text-align: start)を使います。 - 制約:レイアウトの向きが変わらない場合でも、物理的な相当表現(例:
padding-top、width、cqw、vw、text-align: left)は避けます。 - 理由:ボックスモデルの軸を、Flexbox と Grid がすでに使用している軸に揃えられます。また、stylelint-config-modern によってこのルールが適用されます。
- 参考資料:MDN の論理プロパティと値。
- 例:
.card {
max-inline-size: 60ch;
padding-block-start: 2cqi;
text-align: start;
}
2 値の表示(display: block flex)
- ルール:2 値の
display構文を使用します(例:display: block flex、display: inline grid)。 - 制約:1 値のキーワード(例:
display: block、display: flex、display: inline-block)は避けます。 - 理由:ボックスが親要素内でどのように配置されるかと、子要素がどのようにレイアウトされるかを分けられるため、特殊なキーワードである
inline-flexとinline-blockをinline flexとinline flow-rootに置き換えられます。 - 参考資料:MDN の複数キーワードによる
display構文。 - 例:
nav {
display: block flex;
}
.card {
display: block grid;
}
流動的な間隔(cqi)
- ルール:コンテナ単位で間隔のサイズを指定します(例:
padding: 2cqi)。下限や上限が必要な場合は、範囲を制限します。 - 制約:固定の間隔(例:
padding-block: 16px、margin-inline: 1rem)は避けます。 - 理由:間隔がコンテナに応じて拡大縮小するため、コンポーネントは配置場所を問わず適応し、ブレークポイントによる継ぎ目も生じません。
- 参考資料:MDN のコンテナクエリ長さ単位、レスポンシブデザイン:継ぎ目と端。。
- 例:
.card {
container: card / inline-size;
gap: 2cqi;
padding: clamp(1rem, 0.5rem + 2cqi, 2rem);
}
自動的に伸びるテキストエリア(field-sizing: content)
- ルール:テキストエリアに
field-sizing: contentを使用し、lh単位でinline-sizeとmin-block-size/max-block-sizeを明示的に指定します。 - 制約:固定のブロックサイズ(例:
block-size: 8rem)や、入力値の幅に縮小される入力欄でのfield-sizing: contentは避けます。 - 理由:固定ボックス内をスクロールせずに入力したテキストを表示し続けられ、
lhによる範囲指定は文字サイズに応じた行の高さに追従します。 - 参考資料:MDN の
field-sizing。 - 例:
textarea {
field-sizing: content;
inline-size: 100%;
max-block-size: 12lh;
min-block-size: 3lh;
}
タイポグラフィ
流動的な文字サイズ(clamp())
- ルール:コンテナ単位と
clamp()をフォントサイズに使用し、コンテナ幅に応じて比率が大きくなる文字サイズのスケールを採用します。たとえば、狭いコンテナでは Major Second(1.125)、広いコンテナでは Major Third(1.25)を使用します。 - 制約:固定フォントサイズ(例:
px、rem)や、中央の値にrem項を含まない指定(例:clamp(1.75rem, 5cqi, 2.25rem))は避けます。 - 理由:ブレークポイントではなくコンテナに合わせてテキストのサイズを調整でき、
rem項によってズームも機能します。 - 参考資料:MDN の
clamp()、流動的な文字サイズのスケールを設計する。 - 例:
:root {
--medium: clamp(1.3125rem, 1.1821rem + 0.6522cqi, 1.6875rem);
--large: clamp(1.75rem, 1.5761rem + 0.8696cqi, 2.25rem);
}
ウィドウとオーファン(text-wrap)
- ルール:見出しには
text-wrap: balanceを、その他すべてのテキストにはtext-wrap: prettyを使用します。 - 制約:入力欄とテキストエリア以外では、既定の折り返しを避けます。
- 理由:適用を忘れた箇所だけでなく、既定でウィドウとオーファンをなくせます。
- 参考資料:MDN の
text-wrap。 - 例:
h1,
h2,
h3 {
text-wrap: balance;
}
body {
text-wrap: pretty;
}
色
知覚的に均一な明度(oklch())
- ルール:すべての色に
oklch()を使用します。 - 制約:
hex、rgb()、hsl()など、その他の色形式は避けます。 - 理由:明度が知覚的に均一なため、色相が変わってもテキストのコントラストを維持できます。
- 参考資料:MDN の
oklch()。 - 例:
:root {
--success: oklch(40% 0.15 150deg);
--danger: oklch(40% 0.2 25deg);
}
色の設定を尊重する(light-dark())
- ルール:ライトテーマとダークテーマで異なる色には、すべて
light-dark()を使用します。 - 制約:色を再宣言する
prefers-color-schemeブロックや、テーマに応じて変化しない色は避けます。 - 理由:各トークンに両方の値をまとめて指定できるため、色を変更する際にどちらかのテーマを見落とすことがありません。
- 参考資料:MDN の
light-dark()。 - 例:
:root {
--surface: light-dark(oklch(98% 0 0deg), oklch(16% 0 0deg));
--text: light-dark(oklch(35% 0 0deg), oklch(75% 0 0deg));
}
相対色関数(oklch(from /* .. */) と color-mix())
- ルール:相対色構文(例:
oklch(from var(--primary) l c calc(h - 10deg)))またはoklabのcolor-mix()を使って、関連する色を導き出します。 - 制約:ほかの色に関連する色をハードコードしたり、彩度の高い色の明度だけを変更したりしないでください(例:
oklch(from var(--primary) 90% c h))。 - 理由:関連する色は、元の色が変わるとそれに合わせて変化します。
- 参考資料:MDN の相対色、MDN の
color-mix()。 - 例:
:root {
--primary: oklch(55% 0.25 350deg);
--primary-hover: oklch(from var(--primary) l c calc(h - 10deg));
--primary-tint: color-mix(in oklab, var(--primary), oklch(100% 0 0deg) 80%);
}
モーション
モーション設定の尊重(prefers-reduced-motion)
- ルール:大きなアニメーションやトランジションを適用する場合は、
prefers-reduced-motion: no-preferenceを使います。 - 制約:
prefers-reduced-motion: reduceは使わないでください。 - 理由:モーションはユーザーが明示的に選択した場合にのみ適用されるため、アニメーションごとに動きを抑える設定を上書きする必要がありません。
reduceを使うと、すべてのアニメーションに上書き設定が必要になり、見落としやすくなります。 - 参考資料:MDN の
prefers-reduced-motion。 - 例:
@media (prefers-reduced-motion: no-preference) {
.hero {
animation: bounce-in 0.5s ease;
}
}
これらのルールの適用
CSS の作成、リファクタリング、レビューを依頼された場合:
- タスクに適用されるルールを特定します。複数のルールが該当する場合は、すべて適用します。
- 周辺ファイルを読んで、すでに使われているルールを確認し、そのパターンに合わせます。
- 変更箇所だけでなく、変更セット全体に一貫してルールを適用します。
- タスクに適した Modern Web Guidance のガイドを取得して、上記のルールに沿って適用します。その際、上記のブラウザーサポートポリシーにより対象外となるフォールバックは省きます。
- プロジェクトで Stylelint と stylelint-config-modern を使っている場合は、変更したファイルに対して実行し、完了前に報告された問題を修正します。
注意点
上記のルールにも Modern Web Guidance にも記載されていない落とし穴:
clamp()の中央の値にはremの項が必要です:clamp(1.75rem, 1.5761rem + 0.8696cqi, 2.25rem)のようにします。Modern Web Guidance のfluid-scalingガイドにあるclamp(1rem, 5cqi, 2.5rem)の形式ではありません。grid-template-rows: subgridが継承するのは、子要素が要求したトラックだけです。grid-row: span Nと組み合わせてください。spanがないと、子要素は 1 行だけを受け取り、何にも揃いません。Modern Web Guidance のcss-layoutガイドにはspanの記載がありますが、その理由は説明されていません。container-typeはすでに包含を適用します:container-type: inline-sizeを持つコンポーネントにはレイアウト、スタイル、インラインサイズの包含が適用されるため、containは不要です。Modern Web Guidance のperformanceガイドでウィジェットに指定されているcontain: layout style paintは、ペイント包含を追加します。これにより、overflow: clipのように、はみ出した子孫要素が切り取られます。
例
このサイトでは、次のルールに従っています:
src/styles.css:@layersrc/variables.css:oklch()、light-dark()、clamp()src/elements.css:&、text-wrap、prefers-reduced-motion、cqi、2 値のdisplay、field-sizingsrc/components/signpost/signpost.css:@scope
プロジェクトに手本となる独自の CSS がない場合に限り、いずれか 1 つを取得します。