Skip to content

Instantly share code, notes, and snippets.

@tak-dcxi
Last active February 4, 2026 00:45
Show Gist options
  • Select an option

  • Save tak-dcxi/7aa206f63944877b361fb232126919f3 to your computer and use it in GitHub Desktop.

Select an option

Save tak-dcxi/7aa206f63944877b361fb232126919f3 to your computer and use it in GitHub Desktop.
Draft: CSSのレビューの時に見ておくものリスト

Draft: CSSのレビュー時に確認しておくものリスト

  • 下書き途中なのでそのうちまだ増える予定である。
  • あくまで自分用であり、他人にこうレビューしろという意図はない。
  • ある程度書ききったら非公開にする可能性もある。

マインドセット

  • 指摘をする際は、必ず「理由」と「解決策」をセットにする(「この書き方はダメです」だけではレビューにならない)。

  • [MUST] は、仕様・コーディング規約に反するもの/バグが起きているもの/将来的に深刻な問題になりそうなもの/ユーティリティ・アクセシビリティを阻害しているものを優先して扱う。次点として「改善したほうが良いもの」を挙げる。

  • 教義的になりすぎない。ユーザーやクライアントに対してメリットになりにくい/儲けにならないような趣味嗜好が入りうる指摘は IMO として扱うか別の機会で発信する。

    • 私は flex と grid の使い分けについて普段発信しているが、レビューでは挙動に問題がなければ基本的にコメントしない。
    • 私は display の指定には複数キーワード構文を使用しているが、使用したからと言って特別なことができるわけでもないので他の人には強制しない。実務では他者にとって馴染みが深い単一構文を優先して使うこともある。
      • 私が複数キーワード構文を使用する理由は通常フローを display: block flow のように明示できること、他者にレイアウトの仕組みについて教える際に複数キーワード構文を使用したほうが伝えやすいから、みたいな理由である。
    • margin-inline などの論理プロパティを「左右ショートハンドのように扱う」ことに否定的な意見を見かけるが、私は許容している。
      • 前提として、通常フロー/flex/grid/コンテナクエリなど主要なレイアウト手法は、論理的な指定を前提として成立している。flex は縦書き(writing-mode: vertical-lr)になれば主軸が変わり、grid-template は RTL 環境ではセル順が逆転する。一方でメディアクエリ、transformbackground-position などは物理指定しか存在しない。このような物理前提・論理前提が入り乱れた環境下で marginpadding だけを取り上げて物理・論理を議論しても意味がない。
      • これまで justify-content: flex-end を「右揃え」と呼んでも問題視されにくかったのに、marginpadding の論理プロパティに対して突然強く批判するのは不自然に感じる。justify-content には right のような物理指定も存在するが、使用されているのを見たことがない。物理・論理に拘るのであればそのあたりにも言及するべきではないだろうか。
      • margin-inline: auto のような使い方であれば RTL 環境でも同じ結果になりやすく、問題化しにくい。縦書きはデザイン都合でしか発生しないため、考慮対象ではない。むしろ、ブロック軸の margin を不要に上書きしてしまう潜在的不具合を抑えられるなら多少の認識差があっても採用する価値がある。
      • なお、私自身は発信上のコードでは topinset-block-start とするなど、論理プロパティをなるべく優先して使っている。ただしこれは多言語対応のためというより、主要なレイアウト手法と整合させたいという私的な理由が大きく、他者に強制する意図はない。実務では他者にとって馴染みが深い物理プロパティを優先して使うこともある。
      • CSS の有識者が多く関わっている Tailwind でさえ、 v4 から mxmargin-inlinepypadding-block に置き換わっている。[https://tailwindcss.com/docs/margin]
  • 意図が読み取れない記述には [Q] を付けて、質問としてコメントする。

  • レビュイーが書いた CSS は、レビューを通した時点でレビュアーも責任を負うことを自覚する。

仕様やコーディングガイドラインに則っているか

  • プロジェクトでコーディングガイドラインが定められている場合は、それを厳守できているかを最優先で確認する。
  • タイポグラフィ、カラーパレット、ボーダー、エレベーション、アニメーションイージングなどのデザイントークンで定められている定数は tokens (Sass 系の CSS 設計法では setting にあたる)ディレクトリにジャンル毎にファイルを作成してカスタムプロパティに保存する。なるべくそれを指定するように勧める。
    • tokens ディレクトリは CSS から参照できるデザインガイドとして活用する.。
    • 一方でスペーシング系のトークンについてはあまり指摘しない。デザイントークン上では静的カンプという都合上、主に pxrem で定義されることになる。しかし、レスポンシブ環境下での各余白は %cqi が絡むことが多く、 px or rem<meta name="text-scale" /> の存在を考慮するならば使い分けをする必要があり、二者択一で片付けられない。
  • line-height: 1.8 のような指定があった場合は、トークンとして定義されているものであればそれを使用するように指摘する。一方でデザイントークンとして定義されていない特異的なものであればトークンに追加せず直書きを推奨する。

インデントやプロパティの記述順は揃っているか

  • 本来この種の整形はフォーマッター/リンターの責務であり、人間が手作業で揃える仕事ではない。ただし、何らかの事情で自動化が機能していない場合は指摘する。
  • インデントやプロパティ順は「統一されていること」自体が重要であり、方式そのものはプロジェクトの合意が取れていれば基本的に何でもよい。

プロジェクトで定められている動作環境を満たしているか

  • 例として「Safari は最新メジャーバージョンの 2 つ前の最新版までを動作対象とする」という前提があるなら、現時点で Safari 17.6 で未サポートの CSS を使っていないかを確認する。
  • ただし、プログレッシブ・エンハンスメントの範囲で利用でき、品質チェックでも問題になっていないのであれば指摘しない。
    • プログレッシブ・エンハンスメントとして利用可能なもの
      • 未サポート環境では従来どおりに表示される or フォールバックが容易であり、サポート環境ではより良い体験を提供できるもの 。
      • 例:View Transition API、text-wrap: prettyword-break: auto-phrase、アニメーション目的の ::details-contentfield-sizing など
      • ハーフレディングを除去する text-box-trimも非サポートの環境でレイアウトが崩れる/表示が著しく変化するとかでなければプログレッシブ・エンハンスメントの範囲内だと考えている。
    • プログレッシブ・エンハンスメントとして利用しづらいもの
      • 未サポート環境で指定自体が無視されやすいアットルール、または表示崩れが大きくなりやすいレイアウト関連の機能 。
      • 例:スタイルクエリ、Anchor Positioning、position: absolute / fixed に対する place-selfsibling-index() / sibling-count() 関数など
  • さらに、Chrome が先行実装しがちな「既存プロパティの新仕様」にも注意する。Chrome 最新版でのみ確認していると、意図せず差分にハマる可能性がある。
    • ブロックレイアウトでも justify-items / justify-self は使用できるが、現時点で Chrome のみ。レスポンシブで gridblock にロールバックする場合、justify-items / justify-self の定義が残ったままになっていないか確認する。
    • calc(40px / 1280px * 100vw) のような「単位付きの除算」は typed arithmetic と呼ばれ、近年各ブラウザでサポートが進みつつある。
      • ただし現時点では Firefox が未サポートで、Safari も 18.2 以降でのサポートに留まるため扱いづらい。利用は控える。
      • 初学者が意図せず使ってしまうケースも積み上げポストなどで見かけるため、レビューでは特に注意する。
    • Chrome 145 から条件下で vw がスクロールバーを含まずに計算される挙動に変わった。:https://x.com/tak_dcxi/status/2012336570293248061
      • 一方で Safari / Firefox では従来どおり横スクロールが発生し得るため、width: 100vw の使用は避ける。
      • そもそも width: 100vw は多くの場合「不要」または「別手段で置き換え可能」であり、全コアブラウザでスクロールバーを含まずに計算される挙動に置き換わったとしてもコードスメルを減らす目的で指摘対象になり得る。もっと言うのであれば vw 自体 Chrome でズームされないなどリスキーな単位なので控えて使用した方が良い。

命名の規則に一貫性はあるか・衝突のリスクはないか

  • class やカスタムプロパティの命名に一貫性があるかを確認する。
    • ケバブケース/スネークケース/キャメルケースが混在していないか。
    • BEM とそれ以外の命名規則が混在していないか。
  • .title のような汎用的なエレメント名は Scoped CSS によってスコープされているか確認する。
  • コンポーネントの値を操作する API 的カスタムプロパティは、衝突防止と異なる API 的カスタムプロパティが混在した際に「どのコンポーネントのものか」が判別できるよう、--foreground のような汎用名ではなく --component-name--foreground のように対象をプレフィックスで表現する方針を勧める。
  • すべてがグローバル定義になる @keyframes も、衝突防止のためコンポーネント固有であれば同様にプレフィックスを付与することを勧める。
    • 教義的になる部分なのでレビューで指摘しないが、最近は @keyframes など「ユーザーが命名する識別子」には -- を付けるように提案している。
      • 近年の CSS では、ユーザー定義の名前を明確に区別するために -- を強制する流れがある(例:anchor-nameanimation-timeline など)。
      • 「そのプロパティでは -- が必須か?」を個別に覚える必要が減る。
      • 将来 CSS 標準側のキーワードが増えても、衝突リスクを下げられる。
      • 自分が定義した名前だと即座に判別できる。
    • ただし、これは値として扱われる名前に関する話であり、@layer のように値に関係しないものであれば付与する必要はない。

不要な記述・上書きを行っていないか

  • 余計な指定があるほど、別セレクタ(ベースCSS/レイアウトプリミティブ/コンポーネント内の別ルール)とぶつかった際に「どれが勝つか」「どの値が残るか」が状況依存になり、期待通りに動かないケースが増える。
  • 不要な指定をすると、それを打ち消すための上書きに詳細度バトルが発生して修正コストが上がる可能性がある。
  • 「なんとなく指定してみる」でCSSを書くと設計判断が共有されず、再現性のない実装になりがち。宣言ひとつひとつに「なぜそれが必要か」を説明できる状態にすること。

何にでも width: 100% / height: 100% が指定されていないか

  • 特にありがちなのが「とりあえず width: 100% をぶっこんでみる」というもの。
  • 原則として、<div><p> のように UA スタイルシートで display: block が適用される要素は、初期値(auto)のままで親要素の幅に自然に追従するため、多くのケースで width: 100% は不要である。
  • position: absolute / fixed が適用された要素は置換要素などを除きinset: 0 を指定した時点で利用可能幅いっぱいに広がるため、width: 100% を重ねて書く必要は <img> などを除き基本的にない。
  • height: 100% は無限ループ抑止の都合で「親要素の高さが明示されている」場合にしか期待通りに動作しない。min-height のような提案値では成立しない。
    • 後述するが、明示的な高さ指定そのものがアンチパターンになり得るため、安易な height 固定は避ける必要がある。
    • flex / grid アイテムは stretch が効いていれば height: 100% を書かなくても高さいっぱいになり得るため、不要な指定になりやすい。
  • width: 100% / height: 100% は無意味なだけではなく、ブラウザ差分を踏んで意図しないバグを誘発することがある。
    • 過去にあった事例:Safari では list-style-type が有効な <summary> の子要素で width: 100% を指定をすると不自然な改行が起こる、Firefox では height: 100% が適用された subgrid 要素で不自然な詰めが起こる可能性がある、など。
    • box-sizing を初期値にロールバックする場合(例;padding を含めずに max-width を設定したい場合など)や水平マージンが意図せず適用された場合にオーバーフローを起こすリスクもある。
  • width: 100% は、外部表示形式を block にしても自然には広がらない置換要素(例:<img>)や、フォームパーツ(例:<button>)など「必要性が明確な対象」に限定して使う。
    • inline-block inline-flex のような外部表示形式が inline の要素に width: 100% を指定する場合は、外部表示形式を block に変更することも検討する。この場合は vertical-align の初期値 baseline によって生じる下部の空白の発生を抑えられるメリットもある。
  • 子要素を親要素の高さに揃えたいだけであれば、親要素に display: grid を指定するだけで stretch が働き達成できることが多い。
    • この方法なら min-height でも揃えられるため、「高さ固定」というアンチパターンを回避しやすい。
  • Tailwind を使っている場合、何にでも w-full / h-full を付与していないかを確認する。特に生成 AI の出力は乱用されがち。
  • width: min(320px, 100%) のようなフォールバック手段としての 100% なら問題ない。ただし、この場合は水平マージンの存在に気をつける必要がある。
    • 基本的に明示的な水平方向の余白指定は親の padding で行ったほうがよい。

副作用のあるショートハンドプロパティが乱用されていないか

  • ショートハンドは便利な一方で、ロングハンドで積み上げてきた設定が後からショートハンド指定によって意図せずリセットされることがあるため注意する。
  • 例:background: whitebackground-color だけでなく、背景に関する他のサブプロパティも既定値へ戻し得る。結果として、別箇所で設定していた背景画像・サイズ等を壊し、事故につながる可能性がある。
  • 横方向の中央寄せとして margin: 0 auto / margin: auto を学んだ人は多いだろうが、これらは「本来触る必要のない上下のマージン」まで 0 / auto に上書きするのと同等である。
    • 特に Every Layout で紹介される flow レイアウトとの相性が悪く、flow 側で意図していた margin を打ち消してしまう。
    • 代替として、先述したインライン軸だけを中央寄せする margin-inline: auto を提案できる。
  • また、flex のような初期値が別の値にマッピングされるショートハンドにも注意する。「flex: 1 だと何が 1 なのか分からないから flex-grow: 1 にする」といった発信を見かけたが、flex: 1flex-basis が 0 に置き換わるので別物である。
  • すべてのショートハンドが NG というわけではない。
    • insetborder のように「すべての辺に任意の数値を与える」用途では問題になりにくい。
    • 先述した background であっても、疑似要素のような設定が差し込まれるのが考えづらい場面でも問題は起こりにくい。
    • 指摘対象は、影響範囲が広く、継承や複合的な既定値が絡み、実装上怪しそうな項目に絞る。
  • 特に font は最悪のプロパティであり、リセット目的以外では使わない方針を伝える。

デフォルト値をむやみに再指定していないか

  • ここでいう「デフォルト」とは、CSS の初期値/UA スタイルシートの値/リセット CSS の値/ベース CSS の値を総称したもの。
  • 例:margin: 0<div> に対する display: block などを場当たり的に書いていないかを確認する。意図が薄い場合は設計の一貫性が崩れているサインになりやすい。
    • 「最初の要素だけ上マージンを打ち消す」目的なら、 :not(:first-child) やフクロウセレクタなどを使い、「最初の要素にだけ margin を適用しない」という設計を提案する。
  • ブレイクポイント下で値をデフォルトへロールバックしたい場合など、デフォルト値を明示する必要がある場合は IMO として unset / initial / revert / revert-layer を使い、どの次元へ帰りたいのかを明示するよう勧める。
    • ただし、display の CSS 上の初期値は <div> だろうが <p> だろうが一律 inline であるなど、基礎知識が前提になるため、レビュイーの習熟度に応じて指摘の強度は調整する。
  • :root / body に定義されているベースのタイポグラフィは、原則として継承を利用する。局所での再指定は必要最小限に抑える。

グローバルのCSSに「重い指定」が入っていないか確認する

  • ここでいう「重い指定」はパフォーマンスの重さではなく、影響範囲が大きく、結果として保守性の治安を悪化させやすい指定を指す。

ベースCSSの詳細度は極限まで下げられているか

  • ベースレイヤーで定義するタイプセレクタの詳細度が高いと、後からの上書きが難しくなり、破綻の原因になりやすい。
  • 例として、ベースレイヤーに次のような指定があるケースがある。
    a:link, 
    a:visited {
      color: var(--foreground-link);
    }
  • この指定の問題は詳細度が高い点にある。a(タイプセレクタ)+ :link / :visited(擬似クラス)で詳細度は 0.1.1 になり、class 1つでは上書きできない。
    • その結果として、リンク色を変えるだけのために、 意味の薄いセレクタの組み合わせで詳細度を上げたり、最悪!important を付けるといった対応が増え、治安悪化を招きやすい。
  • 新規プロジェクトでは、まずカスケードレイヤー(@layer)の導入を検討する。
    • レイヤー設計が明確なら、上記のようなセレクタでも詳細度バトルが起きにくくなる。
  • カスケードレイヤーの導入が難しい場合は、ベースのタイプセレクタを原則 :where() で包み、詳細度を 0 に落とすことを推奨する。
    :where(a:any-link) {
      color: var(--foreground-link);
    }
  • ただし :where() には落とし穴もあり、サードパーティ製リセットCSS(例:destyle.css)が :where() を使っておらず、タイプセレクタの詳細度が残っている場合、リセット側が優先されてベースの指定が打ち消される可能性がある。そのため、リセットCSSは可能な限り kiso.css のように :where() によって詳細度が下げられているものを選定する。

ベースCSSで破壊的なプロパティが指定されていないか

  • display / position / margin など、レイアウトに関わるプロパティは非常に繊細で、色や文字周りのプロパティと比べて破壊的になりやすい。
  • 極端な例として、次のような発想は明確に危険だと理解できるだろう。
    • flex を多用するからと言って、div 全部に display: flex を付ける
    • position: absolute の相対先の指定が面倒だからと言って、全称セレクタに position: relative を付ける
  • 上記のような実装はさすがに見かけたことは無いものの、実際には次のような指定が紛れ込むことがある。
    • a をすべて inline-block にする
    • pmax-width を指定する
  • レイアウト系プロパティはコンテキストによって何を定義すべきか or しないかは変化する。ベースのスタイルでレイアウト系の定義が行われている場合、異なるコンテキストでは unset / revert が頻発して手間が増えたり、意図しない不具合を引き起こすといったリスクが高い。
  • レイアウト系プロパティは、リセットCSSで最低限の前提を整えた上で、さらに平坦化が必要な場合にのみ慎重に定義する。ベースでの特異な指定は避けるように伝える。

アクセシビリティを阻害する指定が入っていないか

  • 典型例は、リンクやインタラクティブ要素への outline: none。アウトラインを消すとキーボード操作時にフォーカス位置が見えなくなり、アクセシビリティを阻害するためアンチパターンである。
    • 近年のUAスタイルシートではフォーカスリングは主に :focus-visible のときだけ表示されるため、クリック時に常時アウトラインが出る状況は基本的に起きにくい。
  • 「コーディングしやすいから」という理由で、デフォルトの line-height1 にするアプローチも見かけるが、これもアンチパターンである。
    • デザイン上では1行でも、コンテンツ変動やレスポンシブ・機械翻訳などで改行が発生しうる。フォント次第では行が重なり、読めなくなる可能性がある。
    • ベースでは、アクセシビリティの観点から line-height: 1.5 以上を基準に指定し、継承させるように進言する。
    • デザイン上どうしても line-height: 1 が必要な箇所があっても、text-box-trimcalc((1lh - 1em) / 2) でハーフレディングを算出して詰めるなどの代替手段は存在する。
  • ダークモード対応が行われていないのに color-scheme: light dark が設定されていないかも確認する。
    • 背景色やテキスト色の指定が不足していると、ダークモード時にシステムカラーが適用され、文字色と背景色が同化するなどの事故が起こりうる。
    • 海外製のリセットCSSやフレームワーク由来の可能性もあるが、ダークモード非対応なら color-scheme: light dark は取り除くよう指摘する。
    • 暗い背景のサイトであれば、<meta name="color-scheme" content="dark"> を設定するとよい。ユーザーの外観モードに左右されず、スクロールバーなども暗い見た目に寄り、体験の一貫性が上がる。
    • 明るい背景のサイトでは、color-scheme については特に何も指定しない方針で問題ない。

defensive な CSS が書けているか確認する

  • 一度リリースしたら終わりの短期LPは別として、一般的なWebサイトは継続的に更新・改修が発生する。とくにCMS案件では、クライアント都合でイレギュラーなコンテンツ差し替えが起こりうる。そうした状況を前提に、defensive(防衛的)なCSSになっているかを確認する。
  • CSSを書くときは常に「もし〜だったら?」を自問する。たとえば「テキストが2倍の長さになったら?」「画像が存在しなかったら?」など、想定外のパターンをデザイン・開発の段階から織り込んで考える。

コンテンツの変動に対応できているか

  • 固定値の width はオーバーフローを起こしやすく、レスポンシブとも相性が悪いので原則避ける。
    • max-width で目的を満たせるなら、まずそれを採用する。
    • 「内容の幅に寄せたい」意図があるなら width: fit-content を検討する。
    • 固定値の width を許容するのは、アイコン・チェックボックス・細かな装飾など、グラフィカルな要素に限定する。
  • オーバーフローを起こしがちな「幅指定」全般に注意を払う。
    • grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)) は書きがちだが、親要素の幅が 360px を下回るとオーバーフローする。grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)) のように min()100% をフォールバックとして与える。
    • min-width: 320px のような最小幅指定も同様に、320px を下回るとオーバーフローする。min-width: min(320px, 100%) のようにフォールバックを忘れない。
    • flex では flex-shrink: 0 の乱用に注意する。とくに「テキストを変に改行させたくない」という理由で付けられがちなので、まずは min-width: fit-content で解決できないか提案する。
      • ただし、アイコンのように shrink させたくない要素には flex-shrink: 0 が適用されているか確認する。
      • なお、flexアイテムの width: 320pxflex-basis として参照されるため、固定値というより「推奨値」として扱われる。そのため flex-shrink: 0 が指定されていない限り、min(320px, 100%) のようなフォールバックは不要。
      • widthflex-basis の違いは「主軸方向の基準サイズをどちらで持つか」にある。width: 320pxflex-direction に関係なく「横幅」だが、flex-basis: 320pxflex-direction: column では「高さ」になる。ブレイクポイントで flex-direction を切り替える場合は注意する。
      • 横幅まわりの優先順位は、min-width > max-width > flex-basis > width の順に強い。
  • 固定値の height もコンテンツ変動・レスポンシブ変動に弱いので、原則避ける。
    • padding で意図を再現できるなら height 系の指定は不要。
    • 画面いっぱいのヒーローヘッダーなど、明確に高さが必要な場合は min-height を使う。
    • 縦横比が決まっているグラフィカル要素や、正方形・正円が望まれるボックスには aspect-ratio を使い、片側のサイズに追従しつつ比率を維持する。
    • これらで意図が再現できないケースに限り、最終手段として height を使う。
  • 要素数の増減が見込まれる箇所の flex では、flex-wrap: wrap が適用されているか確認する。
  • 画像に aspect-ratioheight: 100% を適用している場合、異なる比率の画像が差し込まれても引き伸ばしにならないよう、object-fit: cover が適用されているか確認する。
    • object-fit の初期値 fill が望ましいケースはほぼない。基本は covercontain の二択である。ベースで <img> に一律 object-fit: cover を当て、contain が必要な箇所だけ上書きする運用も選択肢。
  • 改行まわりについて、明確に「指定せざるを得ない理由」がない限り white-space: nowrap は付けない。また、改行が発生しても可読性が保てるよう、適切な line-height が設定されているか確認する。

想定外に対応できているか

  • 画像の上にテキストを載せるレイアウトでは、画像が読み込まれないと背景とテキストが同化する恐れがある。テキストカラーに対して十分なコントラストを持つ背景色がフォールバックとして用意されているか確認する。
  • flex / grid アイテム内で、テキストが長単語だったり、テキスト入力系の <input> や横スクロールする <table> が含まれる場合、枠を突き抜けてオーバーフローする可能性があるため検証する。
    • min-width の初期値 auto は原則 0 相当として扱われるが、flex / grid アイテムでは min-content にマッピングされる(要素の最小サイズ以下には縮ませないルールが働く)ためである。
    • テキストの折り返し問題は :rootoverflow-wrap: anywhere を指定すれば解決できる。kiso.css を採用している場合は指定不要。
    • ただし、ブラウザ側で最小サイズが設定されている <input> や、横スクロールする <table> を含む場合は、依然としてオーバーフローが発生しうる。
    • 解決策として、全称セレクタで min-inline-size: 0 を指定するのを提案する。副作用が完全にないとは言い切れないが、デフォルトの挙動で「なぜかオーバーフローする」という直感に反する問題に悩まされるより、「縮みすぎる」という目に見える問題を直すほうが、簡単で合理的である。
    • 既存サービスの改修では、この min-content 由来の不具合修正が定期的に発生する。であれば、防衛策としてデフォルトで指定しておくほうがよい。
    • Tailwind の作者もこのアプローチを推奨している

コンポーネント設計に問題がないか

  • 各コンポーネントは常に「自分以外のことは何も知らない」状態を目指す。
  • CSS設計としても、コンポーネントは関係ない場所(例:フッター)へドラッグ&ドロップされても崩れにくいことを目標にする。

コンポーネントの抽象化を追求しすぎていないか?

  • 私がプレイするモンスターハンターシリーズには、看板モンスターとしてリオレウスが存在する。
  • リオレウスには原種のほか、亜種/希少種/二つ名個体/歴戦王/ヌシなどの派生種がいる。見た目は色違いに見えても、攻撃パターン/弱点属性/生息地域/ドロップ素材、そして作れる武器・防具まで、実際にはまったく別物である。
  • ありがちな失敗は、これらの派生種を「バリエーション」としてまとめ、「リオレウス」という単一ブロックとして定義してしまうことだ。別物を1ブロックで扱うと機能的同一性が保てず、不要な分岐が増えてブロックが複雑化・肥大化し、保守しづらくなる。さらに、希少種やヌシが後から不要になった場合でも捨てにくい。
  • Webサイトで例えるならカード類が近い。見た目は似ていても「商品カード」と「記事カード」では役割や内包コンテンツが異なる。これらを「カード」としてまとめると、上記と同じ悲劇が起こりうる。
  • このように「見た目」や「大まかなジャンル」だけで共通化せず、機能的にも同一かどうかを判断軸に加える。抽象化は一括変更しやすい反面、不要なものまで一緒に変更されるデメリットにもなる。抽象化はある程度割り切り、将来不要になれば捨てられる設計を目指す。
  • プログラミング言語には、同じことを何度も書かないことを良しとするDRY原則があるが、CSSはプログラミング言語ではない。たとえば ECSS のように、あえて重複を許容し、複雑化に歯止めをかける思想もある。
  • カードの見た目を共通化したいなら、--card-background のようなトークンを用意し、それぞれのカードコンポーネントへ適用する。こうしておけば「カード類の背景色をこれに変えてください」といったオーダーにも対応しやすい。

「外部関連型レイアウト」がコンポーネントルートに指定されていないか?

  • 「外部関連型レイアウト」は私の造語だが、レイアウト指定には paddinggrid-template のように自身のスタイルだけで完結する「内部関連型レイアウト」と、外部要素との関係で成立する「外部関連型レイアウト」があると考えている。
  • 外部関連型レイアウトに該当しうるプロパティの例は以下。
    • margin は周囲との関係で成立するため外部関連型である。「コンポーネントに margin を持たせるな」と言われがちな理由もここにある。
    • position: absolute は相対先の要素があって成立する。加えて inset も相対先との距離を定義するため外部関連型である。
    • flex / grid-area / justify-self / align-self など、親側の指定とセットで成立するプロパティ。
    • width / height をコンポーネント自身が持つと汎用性が落ち、多くの場合は親要素が制御するのが望ましいため、原則として外部関連型として扱う。
    • min-width / max-width 系は文脈次第で性質が変わる。「このコンポーネントは最低この大きさが必要」「これ以上広げてはいけない」など、コンポーネント固有のルールがあるなら内部関連型。一方で「親要素に対して最大 50%」のように親との関係で決まるなら外部関連型。
    • subgrid は「内部関連型」でありつつ、親が grid であることを前提にする点で「外部関連型」の性質も併せ持つ。subgrid を使うカード類は、それを並べる grid を指定した要素も含めてコンポーネントとして扱う。
  • ただし例外もある。
    • モーダルのように position: fixed を適用する要素はビューポート基準であり、ビューポートの持ち物であることが明白なので、コンポーネント側に外部関連型レイアウトを持たせることを許容する(ビューポートという大枠から操作しづらい事情もある)。
    • グローバルヘッダーのように position: sticky を適用する要素は、粘着先がビューポートなら inset をコンポーネントに持たせてもよい。一方で、テーブルヘッダーのように特定ボックスを粘着先とするなら持たせない(そもそもテーブルヘッダーをコンポーネント化するケースは稀だが念のため)。
    • アイコンやチェックボックスのような小さなグラフィカル要素は、自身が一定サイズを持つ必要があるため内部関連型になる。この場合は width: 1em などを指定し、親の font-size で制御できるようにしておくとよい。

別コンポーネントのセレクタが含まれていないか確認する

  • 親コンポーネントから子コンポーネントのスタイルを上書きする際、子コンポーネントのセレクタを直接操作するのは避ける。詳細度バトルを招きやすく、子コンポーネントの構造変更に引きずられ、親側の定義修正が必要になるなど副作用が大きい。
  • 子コンポーネントの上書き手段には Modifier を追加するなど複数の選択肢があるが、私は子コンポーネントに API 的カスタムプロパティを用意する方法を第一に考えている。
  • 例:子コンポーネントの子要素の border-radius12px24px にしたい場合
    • 子コンポーネント側:上書き対象を border-radius: var(--component-name--radius, 12px) のように置き換える。
    • 親コンポーネント側:任意の箇所で --component-name--radius: 24px を定義する。
    • これにより、継承を利用して安全に子コンポーネントの見た目を差し替えられる。
  • API 的カスタムプロパティを使うメリットは次のとおりである。
    • カスタムプロパティ経由にすることで 詳細度を分離でき、詳細度バトルを避けられる。
    • 親コンポーネントは、子コンポーネント内のどこでAPI 的カスタムプロパティが使われているかを知る必要がない。子コンポーネントの構造が変わっても、親側は基本的に変更不要である。
    • 子コンポーネントは、親側のどこでAPI 的カスタムプロパティが定義されるかを知る必要がない。親ルート、ラッパー、あるいは子側の style 属性など、継承で伝達できる場所ならどこでもよい。
      • style 属性は優先度が高く避けられがちだが、API 的カスタムプロパティの注入手段として捉えるなら、安全かつ有効に使える。
    • Shadow DOM や Scoped CSS でも、継承が有効なら容易に受け渡しできる。
    • 将来的にスタイルクエリとも相性が良い。
  • API 的カスタムプロパティは、衝突防止と識別性のために --foreground のような汎用名ではなく、--component-name--foreground のようにプレフィックスを付ける方針とする。
  • このテクニックは新規案件だけでなく、既存サービスの改修(特に詳細度が破綻している CSS)でも重要なので、覚えておくとよい。

z-index の扱い

  • z-index については登壇資料を参照してほしい
  • コンポーネントルートには 「絶対的な z-index」のみを指定して、コンポーネント内部の各要素には 「相対的な z-index」のみを指定する。
  • 相対的な z-index1 または -1 以外を許容しない。さらに厳密にするなら、登壇資料の方針どおりカスタムプロパティ経由以外を禁止してもよい。
  • 相対的な z-index を持つコンポーネントでは、コンポーネントルートに isolation: isolate を指定してスタッキングコンテキストを生成し、z-index がスコープされていることを確認する。
    • これが漏れると、他コンポーネントの z-index と干渉して意図しない副作用が起こりうる。
  • <dialog> などトップレイヤー要素であっても、現時点では z-index: calc(infinity) を指定する運用を推奨する。
    • transition-property: overlay は現時点で Chrome のみ対応で、Safari など非対応環境では、閉じるアニメーション中に z-index の高い要素の下に回り込み、不自然な挙動になる可能性があるため。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment