【実務・中級編】 first-child疑似クラスの挙動と注意点 – CSS実践ガイド

おい、調子はどうだ?
今日も元気に `display: flex;` でレイアウトと格闘していることだろう。

さて、今回はCSSの基礎の基礎、しかし実務の現場では「えっ、なんで効かないの?」と中級エンジニアの心を幾度となくへし折ってきた魔物、`:first-child` 疑似クラスについて徹底的に解剖していくぞ。

公式ドキュメントを読めば「親要素の最初の子要素を選択する」とだけ書いてある。だがな、実際の案件でその知識だけで挑むと、デザイナーから上がってきた複雑なコンポーネントを組むときに盛大にハマる。

今日は、ブラウザが裏側でどうやってこいつを処理しているのかという仕様のコアから、現場で即座に使える実践的なテクニックまで、俺がみっちり叩き込んでやる。コーヒーでも飲みながら、じっくり読んでくれ。

—

1. `:first-child` の仕様の核心:ブラウザはDOMをどう見ているか?

まず、大前提として叩き込んでおいてほしい。`:first-child` は、「指定したクラスやタグの最初の子」を探しているわけじゃない。「親から見て、自分自身が『最初の子要素(First Child)』であるかどうか」を判定しているんだ。

ここ、めちゃくちゃ重要だからもう一回言うぞ。
選択される側(子要素)の自己申告ではなく、親から見た順番がすべてなのだ。

ブラウザのレンダリングエンジンは、HTMLをパースしてDOMツリーを構築するとき、各要素の兄弟関係(Sibling)をインデックス番号のように管理している。`:first-child` が評価される瞬間、ブラウザは裏側でこんな思考をしている。

1. 「お、この要素に `:first-child` がついているな」
2. 「じゃあ、この要素の `parentNode`(親)はどこだ?」
3. 「その親の `childNodes` の先頭(インデックス0)にいるのは、この要素自身か?」
4. 「YESならスタイルを適用、NOならスルーだ」

この仕組みを理解していないと、次の章で話す「罠」に綺麗にハマることになる。

—

2. 現場で一番多い誤解:要素型が混在したときの挙動

よくあるのが、こういうマークアップだ。

ここで、君が「最初の段落(pタグ)だけに上のマージンを消したいな」と思って、こう書いたとする。

/ やりがちな間違ったコード /
.card-list p:first-child {
margin-top: 0;
}

おいおい、どうなると思う?
答えは、「このスタイルは絶対に適用されない」だ。

なぜか? ブラウザの足取りを追ってみよう。
`.card-list`(親)の最初の子要素(`childNodes[0]`)は、`p` タグではなく `h2` タグだ。
ブラウザは「よし、最初の子供は `h2` だな」と確認し、その次に `p:first-child` を評価する。
「あれ? 今選ぼうとしている `p` タグは、親から見て最初の子か? いや、先頭に `h2` がいるから2番目(あるいはそれ以降)だな。条件不一致!」ということで、スタイルの適用は見送られる。

つまり、`:first-child` は「タグの種類(型)を無視して、文字通り物理的な長男・長女」を指すのだ。

—

3. 実務で使える!綺麗なコードと実践的な解決策

じゃあ、さっきみたいに「親の中に違う種類のタグが混ざっているけど、特定のタグの最初のものだけを選択したい」ときはどうすればいい?
ここで登場するのが、お馴染みの `:first-of-type` との使い分け、そして現代CSSのスマートなアプローチだ。

パターンA:特定の要素型の中で最初を狙うなら `:first-of-type`

先ほどの例で、`h2` があろうとも「`p` タグの中で最初っ面のもの」を狙いたいなら、選ぶべきは `:first-of-type` だ。

/ pタグの中で最初のものにヒットする /
.card-list p:first-of-type {
margin-top: 0;
}

ただし、これも注意が必要だ。もし `.card-list` の中に別の場所で使われた `p` タグが動的に挿入されたりすると、思わぬところが巻き添えを食うことがある。大規模な設計では少し慎重になるべきだ。

パターンB:コンポーネント設計での正しい `:first-child` の使い方

実務の現場(BEMやTailwind、あるいはCSS Modulesなど)では、そもそもHTML構造を綺麗に保つのがプロの仕事だ。
余計な見出しや装飾を同じ階層に混ぜ込まず、コンポーネントのラッパーを一枚噛ませるのが一番の安全策になる。

ここに、コピペしてそのまま現場で使える実践的なコードを用意した。リファレンスとして持っておきな。

/ =================================モーダルやカードのリスト等で使える実践的スタイル ================= /

.card-container {
display: flex;
flex-direction: column;
gap: 1.5rem; / 子要素間の余白を一括管理 /
}

/
【基本のキ】
純粋に「親要素の直下にある最初の子要素」に対してスタイルを当てる。
リストアイテムがすべて同じliタグ等で構成されている場合は、これで100%意図通りに動く。
/
.card-container > :first-child {
padding-top: 0;
border-top: none;
}

/
【応用:動的なCMS出力など、何が入ってくるか分からないエリアの対策】
CMS(WordPressやヘッドレスCMS)から出力される記事本文エリアを想定。
h2, p, ul などタグが入り乱れるカオスな環境でも、
「最初に出てきた要素」の上のマージンをリセットしたい場合の鉄板テクニック。
/
.article-body > :first-child {
margin-top: 0 !important; / ※プロジェクトの設計方針に合わせて調整してくれ /
}

/
【おまけのモダンCSS::not() や :has() との組み合わせ】
最近のブラウザ事情なら :has() を使って「h2を持つコンテナの最初のp」といった
高度な制御も可能だが、まずは基本の :first-child の挙動を完璧に体に叩き込むのが先決だ。
/

—

シニアからのまとめ・アドバイス

`:first-child` は非常に強力なセレクタだが、その挙動は「親から見た絶対的な位置」に依存しているという点を絶対に忘れるな。

  • すべて同じタグが並ぶリスト(`ul > li` など): `:first-child` で完璧に制御できる。安心して使え。
  • 異なるタグが混在するコンテナ(`div > h2, p, div` など): `:first-child` を使うと「最初の要素」にしか当たらないので、意図しないバグの温床になる。その場合はHTMLの構造を見直すか、`:first-of-type` や親要素のクラス設計でカバーしろ。

CSSは、ブラウザが裏側でどう解釈しているかの「メンタルモデル」を頭の中に正しく持っているエンジニアが一番強い。
「なんで効かないんだ?」と悩んだら、感情的にならずにDOMツリーと親要素の構造をもう一度じっくり眺めてみることだ。

よし、今日の知見を早速チームのコードレビューや実装に活かしてくれ。
他に気になるところがあれば、いつでも俺のところに来い。もっとディープな話をしようぜ!

コメント

タイトルとURLをコピーしました