CSSで条件制御!知っておくべきテクニック集

はい、承知いたしました。CSSにおける条件制御に関する詳細な記事(約5000語)を作成し、直接表示します。


CSSで条件制御!知っておくべきテクニック集

Webデザインやフロントエンド開発において、単一の静的なスタイルシートで多様なデバイス、ユーザー設定、要素の状態、さらには親コンテナのサイズに柔軟に対応することは不可欠です。しかし、CSS自体にはプログラミング言語のような if-else 文やループ構造といった明示的な条件分岐の機能は存在しません

では、どのようにして「特定の条件を満たす場合にのみ、特定のスタイルを適用する」という、実質的な「条件制御」を実現するのでしょうか?

それは、CSSが持つ様々な仕組みやルールを巧妙に組み合わせることで実現されます。CSSのセレクター、疑似クラス、疑似要素、アットルール(@rules)、カスケーディング、継承といった基本的な概念に加え、メディアクエリ、コンテナクエリ、CSS変数、そして必要に応じてJavaScriptとの連携など、多岐にわたるテクニックが存在します。

本記事では、CSSにおける「条件制御」を可能にする、知っておくべき主要なテクニックを網羅的に解説します。それぞれのテクニックの目的、基本的な使い方、コード例、メリット・デメリット、応用例、そして注意点などを詳細に掘り下げていきます。これらのテクニックを習得することで、より柔軟で、レスポンシブで、アクセシブルで、そして保守性の高いCSSを書くことができるようになるでしょう。

1. はじめに:CSSと「条件制御」

CSS(Cascading Style Sheets)は、HTMLやXMLなどのマークアップ言語で記述されたドキュメントの見た目を記述するためのスタイルシート言語です。その設計思想は、「スタイルを適用する」ことであり、複雑なロジックを記述することには向いていません。これが、CSSに直接的な if 文のような制御構造がない主な理由です。

しかし、現実のWebサイトやアプリケーションでは、ユーザーの閲覧環境(デバイスの画面サイズや解像度)、ユーザーの設定(ダークモード、ハイコントラストなど)、要素のインタラクション状態(ホバー、フォーカスなど)、さらには個々のコンポーネントが配置されるコンテナのサイズなど、様々な「条件」によって表示を変える必要があります。

ここでいう「条件制御」とは、CSSにおいて「特定の条件(condition)が満たされた場合にのみ、一連のスタイルルール(declarations)を適用する」ことを指します。これは、以下のような様々な方法で実現されます。

  • 環境に基づく制御: デバイスの特性やユーザーのシステム設定に応じたスタイル変更(例: レスポンシブデザイン、ダークモード)。
  • 状態に基づく制御: 要素の現在の状態やユーザーとのインタラクションに応じたスタイル変更(例: ボタンのホバー効果、入力フィールドのバリデーション表示)。
  • 構造・関係に基づく制御: 要素が持つ属性、クラス、あるいは他の要素との関係性に応じたスタイル変更(例: 特定の子要素を持つ親要素のスタイル)。
  • 機能サポートに基づく制御: ブラウザが特定のCSSプロパティやセレクターをサポートしているかどうかに応じたスタイル変更。

これらの「条件制御」を実現するための主要なテクニックを、以下で詳しく見ていきましょう。

2. 最も基本的で強力な条件制御:メディアクエリ (@media)

目的と概要

メディアクエリは、CSSにおける条件制御の最も基本的かつ重要なテクニックであり、レスポンシブWebデザインを実現する上で欠かせません。ビューポート(表示領域)の幅、高さ、デバイスの向き(縦向き/横向き)、解像度、印刷媒体など、閲覧環境の特性に基づいて異なるスタイルを適用することができます。

例えば、「画面幅が特定のサイズより小さい場合は、レイアウトを1カラムにする」「印刷時には、ナビゲーションや背景画像を非表示にする」といった条件指定が可能です。

基本的な構文

メディアクエリは @media アットルールを用いて記述します。基本的な構文は以下のようになります。

css
@media media-type and (media-feature) {
/* 条件を満たす場合に適用されるスタイルルール */
selector {
property: value;
}
}

  • media-type: 適用対象となるメディアの種類を指定します。
    • all: すべてのデバイス(指定しない場合のデフォルト)
    • screen: コンピュータ画面、タブレット、スマートフォンなど
    • print: 印刷プレビュー、印刷されたページ
    • speech: 音声合成装置(スクリーンリーダーなど)
    • tv, projection などもありますが、一般的には screenprint がよく使われます。
  • media-feature: 特定のメディア特性とその値を指定します。最もよく使われるのはビューポートの幅に関するものです。
    • width, height: ビューポートの幅/高さ(正確な値)
    • min-width, max-width: ビューポートの幅の最小値/最大値。レスポンシブデザインで最も頻繁に使用されます。
    • orientation: デバイスの向き(portrait 縦向き, landscape 横向き)
    • resolution: デバイスの解像度(例: 300dpi, 2dppx
    • prefers-color-scheme, prefers-reduced-motion など、ユーザー設定に関するものもあります(これらは後述します)。

例:

“`css
/ デフォルトスタイル(モバイルファーストの場合、小さい画面向けのスタイル) /
body {
font-size: 16px;
}

/ 画面幅が600px以上の場合に適用 /
@media screen and (min-width: 600px) {
body {
font-size: 18px;
}
.container {
display: flex;
}
}

/ 画面幅が992px以上の場合に適用 /
@media screen and (min-width: 992px) {
body {
font-size: 20px;
}
.container {
flex-direction: row;
}
.sidebar {
width: 200px;
}
.main-content {
flex-grow: 1;
}
}

/ 画面幅が992px未満の場合(つまり991px以下)に適用 /
@media screen and (max-width: 991px) {
.sidebar {
display: none; / サイドバーを非表示にする /
}
}
“`

この例では、min-width を使って、画面幅が広くなるにつれてフォントサイズを大きくしたり、レイアウトを切り替えたりしています。これは「モバイルファースト」のアプローチであり、まず狭い画面向けのスタイルを記述し、メディアクエリで徐々に広い画面向けのスタイルを追加していく方法です。逆に、広い画面向けのスタイルを先に記述し、max-width を使って狭い画面向けのスタイルを上書きしていく「デスクトップファースト」のアプローチもあります。どちらを採用するかはプロジェクトによって異なりますが、一般的にはモバイルファーストが推奨されます。

論理演算子

複数の条件を組み合わせるために、論理演算子を使用できます。

  • and: 複数の条件がすべて真である場合に適用。最も一般的。
    • 例: @media screen and (min-width: 600px) and (max-width: 991px) (画面幅が600px以上991px以下の範囲)
  • or (カンマ ,): いずれかの条件が真である場合に適用。
    • 例: @media screen and (min-width: 600px), print (画面または印刷時に適用)
  • not: 条件を否定する場合に適用。
    • 例: @media not screen and (orientation: landscape) (画面で縦向きではない場合に適用 = 画面以外のメディア、または画面で横向きの場合)

メディアクエリを使った具体的なレイアウト例

FlexboxやGridを使ったモダンなレイアウトとメディアクエリは非常に相性が良いです。

例: レスポンシブな3カラムレイアウト

“`html

Item 1
Item 2
Item 3

“`

“`css
/ デフォルト(モバイル向け): 1カラム /
.container {
display: flex;
flex-direction: column; / 縦方向に並べる /
gap: 10px;
}

.item {
background-color: #eee;
padding: 20px;
text-align: center;
}

/ タブレット向け: 2カラム /
@media screen and (min-width: 768px) {
.container {
flex-direction: row; / 横方向に並べる /
flex-wrap: wrap; / 折り返す /
}
.item {
width: calc(50% – 5px); / 2カラム + gapを考慮 /
}
.item:nth-child(odd) {
margin-right: 10px;
}
}

/ デスクトップ向け: 3カラム /
@media screen and (min-width: 1024px) {
.container {
/ flex-direction: row; と flex-wrap: wrap; はそのまま /
}
.item {
width: calc(33.333% – 7px); / 3カラム + gapを考慮 /
}
.item:not(:nth-child(3n)) {
margin-right: 10px;
}
.item:nth-child(3n) {
margin-right: 0;
}
}
“`

このように、異なるブレークポイント(min-width の値)でCSSプロパティの値を変更することで、レイアウトを柔軟に切り替えることができます。

メディアクエリの応用

  • 印刷用スタイル: @media print { ... } を使用して、印刷時に不要な要素(ナビゲーション、広告など)を非表示にしたり、フォントサイズや余白を調整したりします。
  • アクセシビリティ関連のメディア特性: prefers-color-scheme(ダークモードなど)、prefers-reduced-motion(アニメーション削減)、prefers-contrast(コントラスト調整)など、ユーザーのシステム設定に基づいたスタイル調整が可能です(後述)。

注意点

  • ブレークポイントの設計: どこにブレークポイントを設けるかは、デザインやコンテンツに合わせて慎重に検討する必要があります。一般的なデバイスサイズを参考にしつつ、コンテンツが崩れる箇所をチェックして決定するのが良い方法です。
  • メンテナンス性: メディアクエリが増えると、スタイルシートが複雑になり、メンテナンスが難しくなることがあります。CSSプリプロセッサー(Sass, Lessなど)の機能(mixinなど)を活用したり、コンポーネント志向のCSS(CSS Modules, Styled Componentsなど)を導入したりすることで、管理しやすくなります。
  • コンテナクエリとの違い: メディアクエリはビューポートサイズに基づきますが、コンテナクエリは親要素のサイズに基づきます。これは大きな違いであり、コンポーネント設計においてはコンテナクエリがより適している場合があります(後述)。

メディアクエリは、Webサイト全体やページの主要なレイアウトをレスポンシブに対応させるための強力なツールです。

3. 次世代の条件制御:コンテナクエリ (@container)

目的と概要

メディアクエリがビューポート全体のサイズに基づいたスタイル制御を行うのに対し、コンテナクエリは特定の親要素(コンテナ)のサイズに基づいて、その子孫要素のスタイルを制御する機能です。

これは、特にコンポーネントベースのWeb開発において非常に強力です。同じコンポーネント(例: カードUI)を、サイドバーなどの狭い領域に配置した場合と、メインコンテンツ領域などの広い領域に配置した場合とで、コンテナの幅に応じて中の要素のレイアウトや表示を変える、といったことがCSSだけで実現できるようになります。

メディアクエリでは、コンポーネントを配置する場所ではなく、ビューポート全体でブレークポイントを定義するため、同じコンポーネントでも異なるビューポートサイズでは同じように見えてしまうか、あるいは非常に複雑なメディアクエリを書く必要がありました。コンテナクエリは、この問題を解決し、コンポーネントの再利用性と保守性を大幅に向上させます。

メディアクエリとの違い

特徴 メディアクエリ (@media) コンテナクエリ (@container)
基準 ビューポート(表示領域)のサイズ 親要素(コンテナ)のサイズ
制御対象 ドキュメント全体、またはその一部 @container ルールの対象となるコンテナの子孫要素
用途 ページ全体のレイアウト調整、グローバルなスタイル 再利用可能なコンポーネント内のスタイル調整

基本的な構文

コンテナクエリを使用するには、まず親要素に container-type プロパティ、または container shorthand プロパティを設定し、クエリコンテナとして定義する必要があります。

css
/* 親要素をクエリコンテナとして定義 */
.my-container {
container-type: inline-size; /* 水平方向のサイズに基づいてクエリを適用 */
/* container-type: size; /* 幅と高さの両方 */ */
/* container: inline-size / my-container-name; /* 名前を付ける場合 */ */
}

  • container-type: inline-size;: 主に水平方向のサイズ(Writing Modeが横書きの場合は幅)に基づいてクエリを適用することを指定します。最も一般的です。
  • container-type: size;: 幅と高さの両方に基づいてクエリを適用することを指定します。
  • container-type: normal;: デフォルト値。クエリコンテナになりません。
  • 名前付きコンテナ: container: inline-size / my-container-name; のように名前を付けると、複数のコンテナが入れ子になっている場合に、特定のコンテナを指定してクエリを発行できます。

クエリコンテナを定義したら、その子孫要素で @container ルールを使ってスタイルを記述します。

“`css
/ 子孫要素にスタイルを適用 /
.my-container .child-element {
/ デフォルトスタイル /
}

@container (min-width: 400px) {
/ クエリコンテナの幅が400px以上の場合に適用 /
.my-container .child-element {
/ スタイル上書き /
}
}

@container (max-width: 600px) {
/ クエリコンテナの幅が600px以下の場合に適用 /
.my-container .child-element {
/ スタイル上書き /
}
}

/ 名前付きコンテナの場合 /
@container my-container-name (min-width: 500px) {
.my-container .child-element {
/ この名前のコンテナの幅が500px以上の場合に適用 /
}
}
“`

@container ルールの中の条件記述は、基本的にメディアクエリの media-feature と同じ構文を使います(min-width, max-width など)。

コンテナクエリを使った具体的な例:カードコンポーネント

一般的なカードコンポーネントは、そのカードが配置される幅によってレイアウトを変えたい場合があります。狭い場所では縦並びに、広い場所では画像とテキストを横並びに、といった切り替えです。

“`html

Sample Image

カードタイトル

これはカードの短い説明テキストです。コンテナのサイズに応じてレイアウトが変わります。

Sample Image

カードタイトル

これはカードの短い説明テキストです。コンテナのサイズに応じてレイアウトが変わります。

“`

“`css
/ 親コンテナを定義 /
.card-container, .another-container {
container-type: inline-size; / 幅に基づいてクエリを適用 /
/ デモ用 /
width: 300px; / 例: 狭いコンテナ /
border: 1px solid blue;
margin: 20px;
padding: 10px;
}

.another-container {
width: 600px; / 例: 広いコンテナ /
border-color: green;
}

/ デフォルトスタイル(コンテナが狭い場合の縦並び) /
.card-item {
display: flex;
flex-direction: column;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
}

.card-image {
width: 100%; / 縦並びの場合は画像を幅いっぱいに /
height: auto;
}

.card-content {
padding: 15px;
}

.card-title {
margin-top: 0;
}

/ コンテナの幅が400px以上の場合(横並び) /
@container (min-width: 400px) {
.card-item {
flex-direction: row; / 横方向に並べる /
}

.card-image {
width: 150px; / 横並びの場合は固定幅または割合 /
height: auto;
flex-shrink: 0; / 縮小させない /
}

.card-content {
flex-grow: 1; / 残りのスペースを埋める /
}
}
“`

この例では、.card-container.another-container はそれぞれ異なる幅を持っています。.card-container の幅は300pxなので @container (min-width: 400px) の条件を満たしませんが、.another-container の幅は600pxなので条件を満たします。結果として、同じ .card-item コンポーネントでも、.card-container の中では縦並び、.another-container の中では横並びのレイアウトが適用されます。

これはメディアクエリでは難しかった、コンポーネントレベルでの真のレスポンシブ対応を実現します。

メリット

  • コンポーネントの再利用性向上: コンポーネント自体が自身のサイズに応じてスタイルを調整するため、どの場所に配置しても適切に表示されます。
  • 保守性の向上: コンポーネントのスタイル定義が自己完結的になり、親要素やビューポートのグローバルなメディアクエリに依存しにくくなります。
  • 開発効率向上: 各コンポーネントを独立して開発・テストしやすくなります。

デメリット・注意点

  • ブラウザサポート: メディアクエリに比べると比較的新しい機能です。主要なモダンブラウザではサポートされていますが、古いブラウザへの対応が必要な場合はフォールバックや @supports ルールとの組み合わせを検討する必要があります。
  • パフォーマンス: クエリコンテナとその子孫のレイアウト計算には注意が必要です。特に複雑なコンポーネントや多数のコンテナクエリが使用される場合、パフォーマンスへの影響を考慮する必要があります。
  • 入れ子になった場合の挙動: 名前を付けない場合、コンテナクエリはその要素に最も近い祖先のクエリコンテナを参照します。意図しないコンテナを参照しないよう注意が必要です。
  • Containment: container-type プロパティは、その要素にCSS containmentの layoutstyle を自動的に適用します。これにより、コンテナ内部の変更が外部に影響を与えにくくなり、パフォーマンス向上にも寄与しますが、予期しない副作用がないか確認が必要です。

コンテナクエリは、モダンなコンポーネントベースのWebサイト開発において、メディアクエリと並んで非常に重要なテクニックとなるでしょう。

4. 要素の状態に基づくスタイル制御:疑似クラスと属性セレクター

目的と概要

このテクニックは、CSSが最も得意とする領域の一つです。特定の要素が「どのような状態にあるか」に基づいてスタイルを適用します。状態には、ユーザーとのインタラクション(ホバーしているか、クリックされているか)、フォーム要素の値の状態(チェックされているか、無効になっているか)、要素がドキュメントツリーの中でどのような位置にあるか、などが含まれます。

これには主に疑似クラス (:)属性セレクター ([]) を使用します。

インタラクティブな状態に関する疑似クラス

ユーザーの操作に応じたスタイル変更によく使われます。

  • :hover: マウスポインターが要素の上にある場合に適用。
  • :active: 要素がアクティブ化されている(クリックされている、キーが押されているなど)場合に適用。
  • :focus: 要素がフォーカスされている場合に適用。キーボード操作やタブ移動によるフォーカス状態を示すため、アクセシビリティ上非常に重要です。
  • :focus-within: 要素自身か、その子孫要素のいずれかがフォーカスされている場合に適用。フォーム要素を含むコンテナ全体にスタイルを適用する際などに便利です。
  • :focus-visible: フォーカスリングを表示するべき場合に適用。ブラウザのヒューリスティックに基づいて決定され、マウス操作によるフォーカスでは表示されないことが多いです。アクセシビリティとデザインのバランスを取るために推奨されます。
  • :link: 未訪問のリンクに適用。
  • :visited: 訪問済みのリンクに適用。プライバシー上の理由から、スタイルに制限があります(色のみなど)。

例:

“`css
/ ボタンのデフォルトスタイル /
.my-button {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
transition: background-color 0.3s ease; / スムーズな変化 /
}

/ ホバー時のスタイル /
.my-button:hover {
background-color: darkblue;
}

/ クリック時のスタイル /
.my-button:active {
background-color: navy;
}

/ フォーカス時のスタイル (キーボード操作など) /
.my-button:focus-visible {
outline: 2px solid orange; / フォーカスリング /
outline-offset: 2px; / 要素から少し離して表示 /
}

/ 入力フィールドがフォーカスされたら、その親要素に枠線を表示 /
.input-group:focus-within {
border: 1px solid green;
}
“`

フォーム要素の状態に関する疑似クラス

フォームの入力フィールドやチェックボックス、ラジオボタンなどの状態に基づいてスタイルを適用します。

  • :checked: チェックボックス、ラジオボタン、option 要素が選択されている場合に適用。
  • :disabled: 要素が無効化されている場合に適用。
  • :enabled: 要素が有効化されている場合に適用(:disabled の逆)。
  • :read-only: 要素が読み取り専用である場合に適用。
  • :read-write: 要素が編集可能である場合に適用(:read-only の逆)。
  • :valid: 入力値が検証ルールを満たしている場合に適用(HTML5のバリデーション属性 required, type, pattern などを使用)。
  • :invalid: 入力値が検証ルールを満たしていない場合に適用。
  • :required: required 属性を持つ要素に適用。
  • :optional: required 属性を持たない要素に適用。
  • :placeholder-shown: プレースホルダーが表示されている入力要素に適用。

例:

“`css
/ 無効な入力フィールドのスタイル /
input:disabled {
background-color: #eee;
cursor: not-allowed;
}

/ チェックされたチェックボックスの隣接要素のスタイルを変える /
input[type=”checkbox”]:checked + label {
font-weight: bold;
color: green;
}

/ 入力値が不正な場合のスタイル /
input:invalid {
border-color: red;
}

/ 入力値が正当な場合のスタイル /
input:valid {
border-color: green;
}

/ 必須入力フィールドのラベルに印をつける /
label:has(+ input:required)::after { / :has() は後述 /
content: ” *”;
color: red;
}
“`

構造に関する疑似クラス

ドキュメントツリー内での要素の位置関係に基づいてスタイルを適用します。

  • :first-child: 親要素の最初の子要素である場合に適用。
  • :last-child: 親要素の最後の子要素である場合に適用。
  • :nth-child(n): 親要素の子要素の中でn番目である場合に適用(n は数式、キーワード、または数値)。
    • 例: :nth-child(odd) (奇数番目), :nth-child(even) (偶数番目), :nth-child(3n+1) (1番目, 4番目, 7番目…)
  • :only-child: 親要素に対して唯一の子要素である場合に適用。
  • :first-of-type: 親要素の子要素の中で、同じ要素タイプの中で最初の要素である場合に適用。
  • :last-of-type: 親要素の子要素の中で、同じ要素タイプの中で最後の要素である場合に適用。
  • :nth-of-type(n): 親要素の子要素の中で、同じ要素タイプの中でn番目である場合に適用。
  • :only-of-type: 親要素の子要素の中で、同じ要素タイプが唯一である場合に適用。
  • :empty: 子要素を持たない要素(テキストノードやコメントノードも含まない)に適用。

例:

“`css
/ リストの最初のアイテムに太字を適用 /
ul li:first-child {
font-weight: bold;
}

/ テーブルの偶数行に背景色を適用(ゼブラストライプ) /
table tr:nth-child(even) {
background-color: #f2f2f2;
}

/ 親要素に対して唯一の子要素であるdivに特別なスタイル /
.parent > div:only-child {
margin: 20px;
border: 2px dashed blue;
}

/ 子要素が空の要素を非表示にする /
.message:empty {
display: none;
}
“`

論理疑似クラス

複数の疑似クラスやセレクターを組み合わせたり、否定したりするのに便利です。

  • :not(selector): 指定したセレクターにマッチしない要素に適用。
  • :is(selector-list): 指定したセレクターリストのいずれかにマッチする要素に適用。セレクターリストを簡潔に記述できます。
  • :where(selector-list): :is() と似ていますが、特異性が0として扱われます。スタイルライブラリなどで特異性を上げたくない場合に便利です。

例:

“`css
/ class=”active” ではない全てのa要素 /
a:not(.active) {
opacity: 0.8;
}

/ h1, h2, h3 要素のいずれか /
:is(h1, h2, h3) {
margin-top: 1.5em;
}

/ .main-content または .sidebar 内の p 要素 (特異性0) /
:where(.main-content, .sidebar) p {
line-height: 1.8;
}
“`

属性セレクター ([])

要素が特定の属性を持っているか、あるいは特定の属性に特定の値が設定されているかに基づいてスタイルを適用します。data-* 属性や ARIA 属性と組み合わせて、状態や役割を示す際によく使われます。

  • [attribute]: 指定した属性を持つ要素すべてに適用。
  • [attribute="value"]: 指定した属性を持ち、その値が完全に一致する要素に適用。
  • [attribute~="value"]: 指定した属性を持ち、その値がスペース区切りのリストであり、そのリストの中に指定した値が含まれている要素に適用。
  • [attribute|="value"]: 指定した属性を持ち、その値が指定した値で始まるか、または指定した値の後にハイフン (-) が続く要素に適用(言語コードなど)。
  • [attribute^="value"]: 指定した属性を持ち、その値が指定した文字列で始まる要素に適用。
  • [attribute$="value"]: 指定した属性を持ち、その値が指定した文字列で終わる要素に適用。
  • [attribute*="value"]: 指定した属性を持ち、その値に指定した文字列が含まれている要素に適用。

例:

“`css
/ data-state 属性を持つ全ての要素 /
[data-state] {
padding: 5px;
}

/ data-state=”active” の要素 /
[data-state=”active”] {
border: 2px solid blue;
}

/ aria-hidden=”true” の要素 /
[aria-hidden=”true”] {
display: none; / スクリーンリーダーからも隠す要素のスタイル /
}

/ href 属性の値が “.pdf” で終わるリンクにアイコンを表示 /
a[href$=”.pdf”]::after {
content: ” (PDF)”;
font-size: 0.8em;
margin-left: 4px;
}

/ lang=”ja” または lang=”ja-JP” などの要素 /
:lang(ja) {
font-family: “Hiragino Sans”, “Meiryo”, sans-serif;
}
“`

これらの疑似クラスや属性セレクターは、CSSだけで要素の様々な「状態」に対応したスタイル変更を可能にし、ユーザーインタラクションのフィードバック、フォームの利便性向上、要素の特定条件に基づく表示調整などに広く利用されます。JavaScriptと組み合わせることで、より動的な状態変更にも対応できます。

5. 要素間の関係に基づくスタイル制御:Combinator と :has()

目的と概要

CSSのセレクターは、特定の要素だけでなく、要素間の関係性に基づいてスタイルを適用することもできます。親子の関係、兄弟の関係、子孫の関係などを指定することで、「ある要素が特定の条件(子要素や兄弟要素の存在、状態など)を満たす場合に、別の要素(自身を含む)にスタイルを適用する」という条件制御を実現します。

従来は、子や子孫、隣接する兄弟要素に対してスタイルを適用することはできましたが、親要素先行する兄弟要素に対して、後続する要素の状態や存在を条件としてスタイルを適用することはCSS単独では困難でした。しかし、:has() 疑似クラスの登場により、この状況は大きく変わりました。

従来のCombinator

CSSには要素間の関係を指定するためのCombinator(結合子)がいくつかあります。

  • 子孫セレクター (Space): A B – 要素Aの子孫である要素Bに適用。
    • 例: div pdiv 要素の子孫である全ての p 要素)
  • 子セレクター (>): A > B – 要素Aの直接の子要素である要素Bに適用。
    • 例: ul > liul 要素の直接の子要素である全ての li 要素)
  • 隣接兄弟セレクター (+): A + B – 要素Aの直後に続く兄弟要素Bに適用。
    • 例: h2 + ph2 要素の直後に続く p 要素)
  • 一般兄弟セレクター (~): A ~ B – 要素Aの後ろに続く兄弟要素Bに適用(直後でなくても良い)。
    • 例: h2 ~ ph2 要素の後ろに続く全ての p 要素)

これらのCombinatorは、特定の要素が存在する場合に、それと関係のある要素にスタイルを適用するという点で、一種の条件制御と言えます。

例:

“`css
/ .article 要素内の画像に最大幅を適用 /
.article img {
max-width: 100%;
height: auto;
}

/ .parent の直接の子要素である div に枠線を適用 /
.parent > div {
border: 1px solid blue;
}

/ h3 の直後に続く p に特別なマージンを適用 /
h3 + p {
margin-top: 0.5em;
}

/ .error クラスを持つ要素の後ろにある全ての p 要素を赤くする /
.error ~ p {
color: red;
}
“`

親セレクターの代替::has()

:has() 疑似クラスは「参照セレクター」とも呼ばれ、指定したセレクターリストにマッチする要素を子孫または後続の兄弟に持つ要素をマッチさせることができます。これにより、CSS単独では不可能だった「親要素」や「先行する兄弟要素」に対する条件的なスタイル適用が可能になりました。

構文は :has(selector-list) です。:has() の直前のセレクターが、括弧内のセレクターリストにマッチする子孫要素や兄弟要素を持つ場合に、その直前のセレクター要素自身がマッチします。

例:

“`css
/ .container クラスを持つ要素の中で、その子孫に .highlight クラスを持つ要素がある場合 /
.container:has(.highlight) {
border: 2px solid gold; / .container に枠線を付ける /
}

/ class=”checked” のチェックボックスを持つラベル要素 /
label:has(input[type=”checkbox”]:checked) {
font-weight: bold;
color: green; / チェックボックスがチェックされたらラベルを太字緑にする /
}

/ li 要素の中で、その後ろに .active クラスを持つ li 要素がある場合 /
li:has(+ .active) {
opacity: 0.5; / アクティブなリストアイテムの直前のアイテムを薄くする /
}

/ .item クラスを持つ要素の中で、後続の兄弟に .item クラスを持つ要素がある場合 (最後の要素以外) /
.item:has(~ .item) {
margin-right: 10px; / 最後の要素以外のアイテムにマージンを適用 /
}
“`

これらの例は、:has() がいかに強力であるかを示しています。特にフォームのラベルと入力フィールドの関係や、リストアイテム間の関係など、親や先行する兄弟要素の状態に基づいてスタイルを変えたい場合に、JavaScriptを使わずにCSSだけで柔軟な制御が可能になります。

より実践的な :has() の応用例:

  • 入力フィールドに値がある場合にラベルを上に移動させる (Float Label):
    html
    <div class="form-group">
    <input type="text" id="username" required>
    <label for="username">ユーザー名</label>
    </div>

    “`css
    .form-group {
    position: relative;
    margin-top: 20px; / ラベル移動分のスペース確保 /
    }

    .form-group label {
    position: absolute;
    top: 0;
    left: 0;
    transition: all 0.3s ease;
    }

    / inputにフォーカスがあるか、またはinputに値がある(:placeholder-shownではない)場合に、親の.form-groupがスタイル適用対象になる /
    .form-group:has(input:focus),
    .form-group:has(input:not(:placeholder-shown)) {
    / 親要素である.form-group自体にはスタイルを適用しないが、その中の要素に対してスタイルを適用 /
    }

    / 上記:has()がマッチした場合の、子要素であるlabelへのスタイル /
    .form-group:has(input:focus) label,
    .form-group:has(input:not(:placeholder-shown)) label {
    top: -20px; / ラベルを上に移動 /
    font-size: 0.8em;
    color: blue;
    }
    * **特定の子要素(例: 画像)を持つ場合に、親要素の背景色を変える:**html

    Image

    css
    .media-block {
    padding: 15px;
    border: 1px solid #ccc;
    }

    / 子要素にimgを持つ.media-block /
    .media-block:has(img) {
    background-color: #f9f9f9;
    border-color: #a0a0a0;
    }
    “`

:has() の注意点

  • ブラウザサポート: :has() は比較的新しい機能です。主要なモダンブラウザ(Chrome, Firefox, Safari, Edgeなど)の最近のバージョンではサポートされていますが、レガシーブラウザへの対応が必要な場合は利用を避けるか、フォールバックを用意する必要があります。Can I use で最新のサポート状況を確認してください。
  • パフォーマンス: 複雑な :has() セレクターはパフォーマンスに影響を与える可能性があります。特に大規模なDOMツリーに対して頻繁に再計算が必要となるようなセレクターは避けるのが無難です。ただし、ブラウザの実装は日々改善されています。
  • 複雑性: :has() を多用すると、CSSセレクターが複雑になり、コードの可読性やメンテナンス性が低下する可能性があります。使いどころを慎重に検討する必要があります。

:has() は、CSSによる条件制御の可能性を大きく広げる革新的な機能です。従来のCombinatorと組み合わせることで、要素間の複雑な関係性に基づいた高度なスタイリングをCSSだけで実現できるようになります。

6. ユーザー設定に基づくスタイル制御:ユーザープリファレンス関連のメディア特性

目的と概要

ユーザーは、オペレーティングシステム(OS)やブラウザの設定で、Webサイトの表示に関するいくつかの設定を行うことができます。例えば、ダークモードを有効にしたり、アニメーションを減らす設定にしたり、コントラストを上げたりといった設定です。これらの設定は、特にアクセシビリティやユーザーエクスペリエンスの観点から非常に重要です。

CSSでは、これらのユーザー設定(ユーザープリファレンス)をメディアクエリのメディア特性として検出することができます。これにより、「ユーザーがダークモードを設定している場合は、サイトの色を暗くする」「ユーザーがアニメーション削減を設定している場合は、動きを無効にする」といった条件制御が可能になります。

主要なユーザープリファレンス関連のメディア特性

これらの特性は @media ルールの中で使用します。

  • prefers-color-scheme: ユーザーが希望するカラースキーム(ライトモードかダークモードか)を検出します。
    • light: ユーザーがライトモードを希望している。
    • dark: ユーザーがダークモードを希望している。
    • 例: @media (prefers-color-scheme: dark) { ... }
  • prefers-reduced-motion: ユーザーがアニメーションや動きの少ない表示を希望するかどうかを検出します。視差効果や複雑なアニメーションが苦手なユーザー、乗り物酔いしやすいユーザーなどにとって重要です。
    • no-preference: 特に設定なし。
    • reduce: アニメーション削減を希望している。
    • 例: @media (prefers-reduced-motion: reduce) { ... }
  • prefers-contrast: ユーザーがコントラストの高い表示を希望するかどうかを検出します。ロービジョンや色覚異常のあるユーザーにとって重要です。
    • no-preference: 特に設定なし。
    • more: コントラストを高くすることを希望。
    • less: コントラストを低くすることを希望。
    • custom: カスタム設定を使用している(あまり使われない)。
    • 例: @media (prefers-contrast: more) { ... }
  • prefers-reduced-data: ユーザーがデータ使用量を少なくすることを希望するかどうかを検出します。従量課金制の接続や低速なネットワーク環境のユーザーに役立ちます。
    • no-preference: 特に設定なし。
    • reduce: データ使用量の削減を希望。例えば、高解像度画像の代わりに低解像度画像をロードするなどの工夫が考えられます(これはCSS単独ではなく、HTML/JSと連携することが多いですが、CSSで特定の要素を非表示にするといった使い方は可能です)。
    • 例: @media (prefers-reduced-data: reduce) { ... }
  • forced-colors: ユーザーがOSレベルで「強制カラーモード」(ハイコントラストモードなど)を有効にしているかどうかを検出します。このモードでは、OSが多くのスタイルを強制的に上書きするため、CSSでできることは限られます。
    • active: 強制カラーモードが有効。
    • none: 強制カラーモードが無効。
    • 例: @media (forced-colors: active) { ... }

スタイリング例:ダークモード対応 (prefers-color-scheme: dark)

最も一般的な応用例の一つです。

“`css
/ デフォルト(ライトモード)のスタイル /
body {
background-color: #fff;
color: #333;
}

.card {
background-color: #f8f8f8;
border: 1px solid #ddd;
}

/ ダークモード設定時のスタイル /
@media (prefers-color-scheme: dark) {
body {
background-color: #1a1a1a; / 背景を暗く /
color: #f5f5f5; / テキストを明るく /
}

.card {
background-color: #2a2a2a;
border-color: #555;
}

/ 画像の色を反転させる、あるいは別の画像を使うなどの工夫も /
img {
filter: invert(1) hue-rotate(180deg); / 例: 色を反転 /
}
}
“`

CSS変数(Custom Properties)と組み合わせると、さらに効率的にダークモードを実装できます。

“`css
:root {
/ ライトモードのデフォルトカラー /
–background-color: #fff;
–text-color: #333;
–card-background: #f8f8f8;
–card-border: #ddd;
}

/ ダークモード設定時に変数の値を上書き /
@media (prefers-color-scheme: dark) {
:root {
–background-color: #1a1a1a;
–text-color: #f5f5f5;
–card-background: #2a2a2a;
–card-border: #555;
}
}

/ 変数を使用してスタイルを適用 /
body {
background-color: var(–background-color);
color: var(–text-color);
}

.card {
background-color: var(–card-background);
border: 1px solid var(–card-border);
}
“`
このようにCSS変数を使うと、スタイルの変更箇所が変数定義部分に集約され、管理しやすくなります。

スタイリング例:アニメーション削減 (prefers-reduced-motion: reduce)

不要なアニメーションやトランジションを無効化します。

“`css
/ デフォルトのアニメーション /
.box {
transition: transform 0.5s ease;
}

.box:hover {
transform: translateX(10px); / ホバーで動く /
}

/ アニメーション削減設定時 /
@media (prefers-reduced-motion: reduce) {
.box {
transition: none; / トランジションを無効化 /
animation: none !important; / アニメーションを無効化 /
}

.box:hover {
transform: none; / 動きも無効化 /
}

/ 複雑なアニメーション、視差効果などを非表示にする /
.parallax-effect, .complex-animation {
display: none;
}
}
``animation: none !important;transition: none;` は、要素に設定された全てのアニメーション/トランジションを無効にするための一般的な手法です。

重要性

これらのユーザープリファレンスに基づいた条件制御は、Webサイトのアクセシビリティを向上させる上で非常に重要です。多くのユーザーにとってデフォルト設定での表示は問題ありませんが、視覚、認知、平衡感覚などに困難を抱えるユーザーにとっては、これらの設定が快適なWeb体験のために不可欠となる場合があります。ユーザーの設定を尊重し、それに応じたスタイルを提供することは、インクルーシブなWeb開発の基本です。

7. 機能サポートに基づくスタイル制御:@supports ルール

目的と概要

CSSの機能は常に進化しています。新しいプロパティや値、セレクターなどが次々と導入されますが、すべてのブラウザが常に最新の機能をサポートしているわけではありません。@supports アットルールは、ブラウザが特定のCSS機能(プロパティと値の組み合わせ、あるいは特定のセレクター)をサポートしているかどうかを検出するための機能検出(Feature Detection)メカニズムです。

これにより、「もしブラウザがGrid Layoutをサポートしているならこのレイアウトを適用し、サポートしていないならFlexboxでフォールバックする」といった、プログレッシブエンハンスメント(段階的機能向上)グレースフルデグラデーション(穏やかな機能低下)の戦略をCSSだけで実装できます。

基本的な構文

@supports ルールは、括弧内にテストしたいCSSの「宣言」(プロパティと値の組み合わせ)または「セレクター」を記述します。

“`css
/ あるプロパティと値の組み合わせをテスト /
@supports (display: grid) {
.container {
display: grid;
grid-template-columns: 1fr 1fr;
}
}

/ 複数の条件をand, or, not で組み合わせる /
@supports (display: flex) and (gap: 10px) {
.flex-container {
display: flex;
gap: 10px; / flexコンテナでgapが使えるかテスト /
}
}

@supports not (display: grid) {
/ gridをサポートしていないブラウザ向けのフォールバック /
.container {
display: flex;
justify-content: space-between;
}
.container > * {
width: calc(50% – 5px);
}
}

/ 特定のセレクターをテスト (例えば :has() ) /
@supports selector(:has(a)) {
.parent:has(a) {
/ :has() をサポートしている場合のスタイル /
}
}
“`

  • (property: value): 指定したプロパティと値の組み合わせが有効かどうかをテストします。
  • selector(selector): 指定したセレクターが有効かどうかをテストします。比較的新しい機能です。
  • and, or, not: 複数の条件を組み合わせたり、否定したりするための論理演算子です。

機能サポートに基づくスタイリング例

例: Flexbox gap プロパティのフォールバック

Flexboxコンテナの gap プロパティは比較的新しく、古いブラウザではサポートされていません。@supports を使ってフォールバックを記述できます。

“`css
.flex-container {
display: flex;
/ デフォルトまたはフォールバックのスタイル /
/ gapの代わりにmarginを使う /
margin-left: -10px; / コンテナ自体の左マージン /
}

.flex-container > * {
/ gapの代わりにmarginを使う /
margin-left: 10px; / アイテム間のマージン /
}

/ gapがサポートされている場合 /
@supports (gap: 10px) {
.flex-container {
gap: 10px; / gapを使う /
margin-left: 0; / フォールバックのmarginを無効化 /
}

.flex-container > * {
margin-left: 0; / フォールバックのmarginを無効化 /
}
}
“`

このコードは、まず gap がサポートされていない場合(あるいは @supports ルール全体がサポートされていない場合)のフォールバックとして margin を使ったレイアウトを定義します。そして、@supports (gap: 10px) の条件を満たすブラウザでは、gap プロパティを使い、フォールバックの margin を上書きして無効化します。

例: Grid Layout と Flexbox の切り替え

“`css
.layout-container {
/ デフォルト(フォールバック)としてFlexboxレイアウト /
display: flex;
flex-wrap: wrap;
gap: 10px; / もしgapがサポートされていない場合のフォールバックも別途考慮 /
}

.layout-container > * {
width: calc(50% – 5px); / 2カラム /
}

/ Grid Layoutがサポートされている場合 /
@supports (display: grid) {
.layout-container {
display: grid; / Gridレイアウトを使う /
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); / 可変グリッド /
gap: 10px; / Gridならgapは広くサポートされている /
/ Flexbox用のフォールバック設定を無効化 /
flex-wrap: initial;
}

.layout-container > * {
/ Gridアイテムにはwidthは不要 /
width: initial;
}
}
“`

このパターンは、モダンなCSSレイアウト(Gridや最新のFlexbox機能)を使いつつ、それらをサポートしない古いブラウザでも最低限のレイアウトを維持したい場合に非常に有効です。

ベンダープレフィックスの扱い

特定のベンダープレフィックスを持つプロパティのサポートを検出することもできます。

css
@supports (-webkit-appearance: none) or (-moz-appearance: none) or (appearance: none) {
/* ベンダープレフィックス付きまたはプレフィックスなしの appearance プロパティがサポートされている場合 */
input[type="range"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/* カスタムスタイルの適用 */
}
}

メリット

  • 堅牢なフォールバック: ブラウザの機能サポート状況に合わせたスタイルをCSSだけで記述できるため、JavaScriptに依存しない堅牢なプログレッシブエンハンスメントが可能です。
  • コードの明確化: どのスタイルがどの条件で適用されるかが @supports ルールによって明確になります。
  • ブラウザ固有のバグ回避: 特定のブラウザで機能にバグがある場合に、そのブラウザでその機能を使わないように制御することも理論上は可能です(ただし、セレクターを使った詳細なブラウザ判定は難しく、ユーザーエージェントによる判定は推奨されません)。

注意点

  • テストできるのは「サポートされているか」だけ: 特定の機能がバグなく動作するかどうかはテストできません。あくまで構文としてブラウザが認識するかどうかです。
  • @supports 自体のサポート: ごく古いブラウザの中には @supports ルール自体をサポートしていないものもあります。そのようなブラウザでは、@supports ブロック内のスタイル全体が無視されます。そのため、@supports not (...) を使ったフォールバック戦略が推奨されます。つまり、まずデフォルトとして古いブラウザでも動作するスタイルを記述し、@supports ブロック内で新しい機能を適用してデフォルトを上書きする形です。

@supports ルールは、新しいCSS機能を積極的に導入しつつ、幅広いブラウザで互換性を維持したい場合に役立つ、強力な条件制御のテクニックです。

8. CSS変数 (Custom Properties) を利用した間接的な条件制御

目的と概要

CSS変数(カスタムプロパティ、--* 形式)は、CSSファイル内で再利用可能な値を定義するための機能です。これ自体に条件分岐のロジックはありませんが、CSS変数の値を異なる条件(メディアクエリ、コンテナクエリ、あるいはJavaScript)の中で上書きすることで、間接的にスタイルを条件制御できます。

これにより、共通のスタイル構造を維持しつつ、特定の条件下でカラーテーマ、サイズ、スペーシングといった特定のデザイン要素を一括して変更することが容易になります。

CSS変数の基本

CSS変数は :root 疑似クラス(ドキュメントのルート要素、通常は <html>)または特定の要素のスコープ内で定義し、var() 関数を使ってその値を参照します。

“`css
:root {
–primary-color: #007bff;
–spacing-unit: 8px;
}

.button {
background-color: var(–primary-color);
padding: calc(var(–spacing-unit) * 2) var(–spacing-unit);
margin-bottom: var(–spacing-unit);
}
“`

メディアクエリやコンテナクエリ内での変数定義の変更

これがCSS変数を使った間接的な条件制御の典型的なパターンです。

例: レスポンシブなスペーシング

“`css
:root {
–section-padding: 20px; / デフォルトのパディング /
}

@media (min-width: 768px) {
:root {
–section-padding: 40px; / 画面が広い場合はパディングを大きく /
}
}

.section {
padding: var(–section-padding);
}
``
この例では、画面幅によって
–section-paddingというCSS変数の値を変更し、それを.section要素のpadding` プロパティに適用することで、間接的にレスポンシブなパディングを実現しています。

例: コンテナ幅による要素サイズ調整(@container@property との組み合わせも強力ですが、単純な例として)

“`css
.card-container {
container-type: inline-size;
}

.card-image {
–image-size: 100%; / デフォルト /
width: var(–image-size);
height: auto;
}

@container (min-width: 400px) {
.card-image {
–image-size: 150px; / コンテナが広い場合は固定幅 /
}
}
“`
このように、コンテナクエリ内で変数の値を変更し、子孫要素でその変数を使用することで、コンポーネントレベルでの条件制御が実現できます。

JavaScriptからの変数操作

JavaScriptを使って要素のスタイルや :root に定義されたCSS変数の値を動的に変更することで、CSS単独では難しい複雑な条件に基づいたスタイル変更が可能です。

“`html

コンテンツ

“`

“`css
:root {
–bg-color: white;
–text-color: black;
}

.box {
background-color: var(–bg-color);
color: var(–text-color);
padding: 20px;
border: 1px solid #ccc;
}

/ ダークテーマ用のスタイル(JavaScriptで body に dark-theme クラスが付与されることを想定) /
body.dark-theme {
–bg-color: #333;
–text-color: #f5f5f5;
}
“`

“`javascript
const themeToggleBtn = document.getElementById(‘theme-toggle’);
const body = document.body;

themeToggleBtn.addEventListener(‘click’, () => {
body.classList.toggle(‘dark-theme’); // body要素に dark-theme クラスを付け外し
});
“`

この例では、JavaScriptでボタンがクリックされたら <body> 要素に dark-theme クラスを付け外ししています。CSS側では、<body>dark-theme クラスが付いている場合に :root に定義されたCSS変数の値を上書きしています。.box 要素などは、この変数の値に基づいてスタイルが変わるため、間接的にテーマが切り替わります。

これはCSS変数が「変数」として機能することを活かした方法であり、複雑なテーマ切り替えや動的なスタイル調整において非常に有効です。

メリット

  • 一元管理: デザインシステムにおけるカラーパレットやタイポグラフィの設定など、共通の値をCSS変数で一元管理できます。
  • テーマ切り替え: ダークモードやユーザーによるテーマ選択など、複数のテーマを効率的に実装できます。
  • 保守性: スタイルの変更が必要な場合に、変数定義部分を修正するだけで複数の箇所に反映させることができます。
  • ランタイムでの変更: JavaScriptから値を変更することで、ユーザー操作やアプリケーションの状態に基づいた動的なスタイル変更が容易になります。

制限

  • 純粋なロジックは記述できない: CSS変数自体は計算や条件分岐のようなロジックを実行できません。値の変更は、メディアクエリのようなCSSの他の機能やJavaScriptによって行われる必要があります。
  • 型の問題: 変数には型がなく、単なる文字列として扱われます。計算(calc()) などで利用する際に、値の単位などに注意が必要です。

CSS変数は、他のCSSテクニック(メディアクエリ、コンテナクエリ)やJavaScriptと組み合わせて使うことで、スタイルを効率的に管理し、様々な条件に基づいた変更を容易にする強力なツールとなります。

9. カスケーディング、特異性、継承を利用した条件的なスタイル適用

目的と概要

これは明示的な「条件制御構文」というよりは、CSSの基本的なスタイル適用ルールそのものです。しかし、CSSがどのようにスタイルを解決し、最終的にどのスタイルを要素に適用するか(カスケーディング、特異性、継承)を理解し、意図的に利用することで、結果として「特定の条件(より具体的なセレクター、ソースでの位置など)を満たす場合に、デフォルトスタイルを上書きして適用する」という、一種の条件制御を実現しています。

例えば、「全てのリンクは青色だが、ナビゲーションバー内のリンクは黒色にする」という要件は、ナビゲーションバー内のリンクにより特異性の高いセレクターを適用することで実現できます。これは、CSSの特異性のルールを利用した条件制御の一例です。

CSSの適用ルールの復習

ブラウザがどのスタイルルールを要素に適用するかを決定する主なルールは以下の通りです。

  1. 重要度 (Importance): !important が付いている宣言が最も優先されます。ただし、濫用はコードを理解しにくくし、メンテナンスを困難にするため、特別な理由がない限り避けるべきです。
  2. 出典 (Origin): スタイルルールのソース。ユーザーエージェントのスタイルシート < ユーザーのカスタムスタイル < 著者のスタイルシート < 著者の !important < ユーザーの !important の順に優先されます。通常、私たちは「著者のスタイルシート」を操作します。
  3. 特異性 (Specificity): セレクターの「具体的な度合い」を示す指標。より特異性の高いセレクターで記述されたスタイルが優先されます。特異性は以下の3つの要素(およびインラインスタイル)によって計算されます。
    • IDセレクター (#id) の数 (a)
    • クラスセレクター (.class)、属性セレクター ([attribute])、疑似クラス (:pseudo-class) の数 (b)
    • 要素セレクター (element)、疑似要素 (::pseudo-element) の数 (c)
    • インラインスタイルは最も特異性が高く(スコア形式で(1,0,0,0)と考える)、!important はそれをさらに上書きします。
    • スコア形式: (a, b, c)。例えば #nav .list li:hover の特異性は (1, 2, 1) となります。スコアが高い方が優先されます。
  4. ソース順 (Order of Appearance): 特異性が同じ場合、スタイルシート内で後から記述されたルールが優先されます。

特異性の高いセレクターでの上書き

最も一般的な条件制御(デフォルトスタイルからの変更)の方法です。

例:

“`css
/ デフォルトのリンクスタイル (特異性: 0, 0, 1) /
a {
color: blue;
text-decoration: underline;
}

/ ナビゲーション内のリンクスタイル (特異性: 0, 1, 1) /
.navigation a {
color: black;
text-decoration: none;
}

/ アクティブなナビゲーションリンクスタイル (特異性: 0, 2, 1) /
.navigation a.active {
font-weight: bold;
color: red;
}
``
この例では、
.navigation aaよりも特異性が高いため、ナビゲーション内のリンクに適用されます。さらに.navigation a.active` はより特異性が高いため、アクティブなナビゲーションリンクに適用され、それまでのスタイルを上書きします。これは「ナビゲーション内のリンク」や「アクティブなリンク」という条件に基づいてスタイルを適用していると言えます。

ソース順

特異性が完全に同じセレクターが複数ある場合、最後に定義されたものが優先されます。

css
p { color: red; } /* 後から定義された方が優先される */
p { color: blue; } /* 全ての p 要素は青くなる */

意図的にこれを条件制御に使うことは少ないですが、スタイルシートの読み込み順や @import の順序などが最終的なスタイルに影響を与える可能性があるため、理解しておくことは重要です。

!important の利用(避けるべき理由)

!important を宣言の末尾に追加すると、その宣言は特異性やソース順を無視して最も高い優先度(ユーザーの !important を除く)で適用されます。

“`css
.my-element {
color: blue !important; / 常に青くなる /
}

.container .my-element { / 特異性が高いが !important に負ける /
color: red;
}
“`

!important は「このスタイルはどんな状況でも絶対に適用したい」という場合に使うことができますが、他のスタイルを強引に上書きするため、スタイル間の依存関係を複雑にし、後からそのスタイルを変更することが非常に困難になります。特に大規模なプロジェクトやチーム開発では、!important の使用は最後の手段とし、特異性を適切に管理することでスタイルを制御するのがベストプラクティスです。

継承と初期値

一部のCSSプロパティ(color, font-family, text-align など)は子要素に継承されます。これも、親要素に適用されたスタイルが子孫要素にも影響を与えるという点で、一種の条件制御(親のスタイルが条件)とみなすことができます。

また、initial, unset, revert といったキーワードを使って、プロパティの値を初期値に戻したり、継承された値を解除したりすることも、スタイル適用をリセットするという意味で条件制御の一種と言えるかもしれません。

フレームワークや設計手法とカスケーディング

BEM(Block, Element, Modifier)のようなCSS命名規則や、CSS Modules, Styled Components といった技術は、セレクターの特異性を意図的に低く保ったり、スタイルをローカルスコープに閉じ込めたりすることで、カスケーディングによる予期せぬスタイル上書きを防ぎ、スタイル管理を容易にします。これも、カスケーディングというCSSの基本的なルールをコントロールするためのアプローチです。

カスケーディング、特異性、継承は、CSSがどのように機能するかの根幹をなすルールです。これらのルールを深く理解することで、意図的にスタイルを上書きしたり、デフォルトスタイルからの変更を効率的に記述したりといった、基本的なレベルでの条件制御を効果的に行うことができます。複雑なセレクターチェーンを使うことによる特異性の管理は、CSS設計において常に考慮すべき重要な側面です。

10. JavaScriptによるクラス付与による条件制御

目的と概要

CSS単独では検出できないような複雑なアプリケーションの状態や、ユーザーのインタラクション(例: スクロール位置、APIからのデータ取得結果、ユーザーの認証状態など)に基づいてスタイルを適用したい場合があります。このようなケースでは、JavaScriptを使って要素に特定のクラスを付けたり外したりすることで、CSSによるスタイルの適用を制御するのが最も一般的で強力な手法です。

JavaScriptがアプリケーションの状態やイベントを検知し、その結果に応じてDOM要素のクラス属性を操作します。CSS側では、そのクラスが付与されている要素に対してスタイルルールを定義しておきます。

JavaScriptでのクラス操作

JavaScriptでは、element.classList API を使うのがクラス操作の標準的な方法です。

  • element.classList.add('class-name'): クラスを追加する。
  • element.classList.remove('class-name'): クラスを削除する。
  • element.classList.toggle('class-name'): クラスがあれば削除し、なければ追加する(真偽値を第2引数で渡すことも可能)。
  • element.classList.contains('class-name'): 指定したクラスが含まれているか確認する。

CSS側でのスタイル定義

JavaScriptが付与/削除するクラスに対して、CSSでスタイルルールを定義しておきます。

“`html

“`

“`css
/ デフォルトの状態(非表示) /
.hidden {
display: none;
}

/ 表示状態(JavaScriptで付与されるクラス) /
.visible {
display: block;
border: 1px solid #ccc;
padding: 15px;
margin-top: 10px;
}
“`

“`javascript
const toggleButton = document.getElementById(‘toggle-button’);
const detailsDiv = document.getElementById(‘details’);

toggleButton.addEventListener(‘click’, () => {
// hiddenクラスとvisibleクラスを付け替え
detailsDiv.classList.toggle(‘hidden’);
detailsDiv.classList.toggle(‘visible’);

// ボタンのテキストも変更する場合
if (detailsDiv.classList.contains(‘visible’)) {
toggleButton.textContent = ‘詳細を隠す’;
} else {
toggleButton.textContent = ‘詳細を表示’;
}
});
``
この例では、ボタンクリックというユーザーインタラクションをJavaScriptが検知し、詳細を表示する
div要素にhiddenまたはvisible` クラスを付け替えています。CSS側では、これらのクラスに対して表示/非表示のスタイルを定義しているため、CSSによって「詳細の表示状態」という条件に応じたスタイル制御が実現されています。

メリット

  • 柔軟性: CSS単独では検出できない、あらゆる複雑な条件やアプリケーションの状態に基づいてスタイルを制御できます。
  • 表現力: JavaScriptの持つプログラミング能力(条件分岐、ループ、非同期処理など)を活かせるため、非常に細かい制御が可能です。
  • 既存コードとの連携: 既存のJavaScriptベースのロジックやライブラリ、フレームワークと連携しやすいです。

デメリット

  • JavaScriptへの依存: JavaScriptが無効な環境や、JavaScriptがロード・実行される前は機能しません。コアな表示制御には向かない場合があります(ただし、これは現代のWebにおいてはあまり大きな問題にならないことも多いです)。
  • パフォーマンス: DOM操作はパフォーマンスのボトルネックになる可能性があります。特に頻繁なクラス操作や多数の要素への操作は注意が必要です。
  • コードの見通し: スタイルがCSSファイルだけでなく、JavaScriptコードにも分散するため、どのクラスがいつ、なぜ付与されるのかを把握するのが難しくなる場合があります。
  • FOUC (Flash Of Unstyled Content): JavaScriptによるスタイルの適用が遅れると、一時的にスタイルが適用されていない状態が表示されてしまう可能性があります。

いつ使うべきか(CSS優先の原則)

原則として、CSS単独で実現できる条件制御は、可能な限りCSSで行うべきです。

  • 要素のホバー、フォーカスなどの単純なインタラクション状態 → 疑似クラス
  • 画面サイズ、デバイスの向き → メディアクエリ
  • 親コンテナのサイズ → コンテナクエリ
  • 要素の属性やDOMツリー内の位置 → 属性セレクター、構造疑似クラス、Combinator, :has()
  • ブラウザの機能サポート → @supports
  • ユーザーのシステム設定(ダークモード、アニメーション削減など) → ユーザープリファレンス関連のメディア特性

これらのCSS単独で実現できる条件制御は、パフォーマンスが高く、JavaScriptが無効でも機能し、スタイル定義が一箇所にまとまるためメンテナンス性も優れています。

一方、JavaScriptによるクラス操作は、以下のような場合に適しています。

  • ユーザーの入力値に基づいてリアルタイムで複雑なバリデーションを行い、その結果に応じてスタイルを変える。
  • サーバーからのデータ取得結果やユーザーのログイン状態によって要素の表示/非表示やスタイルを切り替える。
  • 要素のスクロール位置に応じてヘッダーを固定表示にする、といったスクロールイベントに基づくスタイル変更。
  • ユーザーがドラッグ&ドロップしている最中の要素に特別なスタイルを適用する。
  • 複数の要素の状態が組み合わさる、複雑な条件に基づくスタイル変更。

JavaScriptによるクラス付与は強力ですが、CSSで完結できる範囲を見極め、適切に使い分けることが重要です。

11. まとめと展望

本記事では、CSSにおける「条件制御」を実現するための様々なテクニックを詳細に解説しました。CSSにはプログラミング言語のような明示的な if-else 文はありませんが、その代わりに豊富な機能やルールを組み合わせることで、多様な条件に基づいて柔軟なスタイル適用が可能になることを理解いただけたかと思います。

紹介した主なテクニックと、それぞれの得意な領域をまとめます。

  • メディアクエリ (@media): ビューポートサイズやデバイス特性に基づくレイアウト、全体的なスタイルの切り替え。レスポンシブデザインの基盤。
  • コンテナクエリ (@container): 特定の親要素(コンテナ)のサイズに基づく、コンポーネント内部のスタイルの切り替え。コンポーネント指向開発との相性が良い。
  • 疑似クラス (:): 要素の状態(ホバー、フォーカス、チェック状態など)、構造(最初の子要素、奇数番目など)、論理的な条件(指定したセレクターにマッチしないなど)に基づくスタイル適用。
  • 属性セレクター ([]): 要素が持つ属性やその値に基づくスタイル適用。data-* 属性やARIA属性との連携に便利。
  • Combinator: 要素間の関係性(親子、兄弟、子孫)に基づくスタイル適用。
  • :has(): 特定の子孫要素や後続の兄弟要素の存在/状態を条件として、親要素や先行する兄弟要素にスタイルを適用。CSS単独での条件制御の可能性を大きく広げた強力な機能。
  • ユーザープリファレンス関連のメディア特性: ユーザーのOS/ブラウザ設定(ダークモード、アニメーション削減など)に基づくスタイル適用。アクセシビリティ向上に不可欠。
  • @supports: ブラウザの特定のCSS機能のサポート状況に基づくスタイル適用。プログレッシブエンハンスメントやフォールバックに役立つ。
  • CSS変数 (--*): 他の条件制御テクニックやJavaScriptと組み合わせて、共通の値を一元管理し、条件に応じて変更することで間接的にスタイルを制御。テーマ切り替えなどに便利。
  • JavaScriptによるクラス付与: CSS単独では検出できない複雑なアプリケーションの状態やユーザーインタラクションに基づき、要素のクラスを操作することでスタイルを制御。CSSとJavaScriptの連携。

これらのテクニックは、それぞれ独立して使うこともあれば、組み合わせて使うことでより複雑な要件に対応することもよくあります。例えば、メディアクエリとCSS変数を組み合わせてレスポンシブなカラースキームを実装したり、JavaScriptで特定の状態クラスを付与し、そのクラスを持つ要素の中でコンテナクエリを使って内部レイアウトを制御したり、といったことが考えられます。

効率的で保守性の高いCSSを書くためには、これらのテクニックを適切に使い分けることが重要です。可能な限りCSS単独で完結できる範囲でスタイルを定義し、CSSでは難しいロジックや外部データに基づく制御が必要な場合にJavaScriptとの連携を検討するのが良いアプローチです。

CSSは進化し続けており、今後もより高度な条件制御やロジックを記述できる機能が追加される可能性があります。例えば、CSS Nesting (& による親セレクター参照) は :has() と組み合わせて書くとより直感的になるケースがありえますし、CSS Variables Level 2 では変数に対する型付けやデフォルト値、さらには簡単な条件式のようなものが検討されているかもしれません(ただし、これは将来の仕様であり確定ではありません)。

これらの新しい機能を常に学び続け、古いテクニックと新しいテクニックを組み合わせて活用することで、よりモダンで、効率的で、ユーザーにとって快適なWeb体験を提供できるスタイルシートを記述していくことができるでしょう。

本記事が、CSSにおける条件制御の多様なテクニックを理解し、日々のコーディングに役立てる一助となれば幸いです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール