LAPLAB:Laugh And Peace Laboratory:あなたのWEB/IT担当にしてください。山形から全国までWEB/ITまるっとおまかせください。

お知らせ

ブログ一覧

blog

短く書けて上書きもしやすいCSS

前回の「:has()で“中身に応じて見た目を出し分ける”」記事(👉 https://lapl.jp/css-has/)に続いて、今回は セレクタを短く書きつつ“上書きのしやすさ”をコントロールできる :is() と :where() を解説します。
どちらも複数の候補をひとまとめにできますが、決定的な違いは特異性(上書きの強さ)。:is() は中で最も強い特異性を引き継ぎ、:where() は常に0で“弱く敷くベース”に最適です。
この記事では 結論→基本→実例→落とし穴→チートシートの順で、コピペOKのコードとともにサクッと整理します。


TL;DR(最初に結論)

  • :is() … 中で一番強い特異性を引き継ぐ → 最終ルールや状態指定に向く
  • :where() … 特異性が常に 0 → ベース・初期化・ユーティリティに向く
  • 迷ったら:仕上げ=:is()/土台=:where()

1. 基本:何が短くなるの?

Before(長い&重複)

.nav li > a:hover,
.nav li > a:focus-visible,
.nav li > a[aria-current="page"] {
  color: var(--primary);
}

After(:is() で短く)

.nav li > a:is(:hover, :focus-visible, [aria-current="page"]) {
  color: var(--primary);
}

さらに“弱く置きたい”ベースなら :where()

/* ベース(あとから簡単に上書きしたい) */
:where(.prose) :where(h1, h2, h3) { margin-block: .8em; }

ポイント::is() と :where() は見た目の適用範囲は同じ。違うのは特異性だけ。


2. 使い分けの感覚(1分理解)

  • :is() …「ちゃんと効いてほしい・負けてほしくない」時に
    例:ホバーやアクティブなどの状態、最後の上書きルール
  • :where() …「あとで上から塗り替えたい」時に
    例:タイポグラフィのベース、ユーティリティの初期化

図でいうと:
強さ :is() >(ふつうのセレクタ)> :where()(常に弱い)


3. よくある実例(コピペOK)

3-1. 見出しまとめ(どれが来ても同じ余白)

.article :is(h1, h2, h3) { margin: .8em 0; }    /* 仕上げ */

ベースにしたいなら:

:where(.article) :where(h1, h2, h3) { margin: .8em 0; }  /* 後から上書きしやすい */

3-2. 擬似状態の束ね(hover / focus / current)

.nav a:is(:hover, :focus-visible, [aria-current="page"]) {
  color: var(--primary);
  text-decoration: underline;
}

3-3. 「画像 or SVG を含まないリンク」だけ下線(:has()と併用)

.article a:not(:has(img, svg)):is(:hover, :focus-visible) {
  text-decoration: underline;
}

3-4. フォーム:いろんな入力要素を一括スタイル

.form :is(input, select, textarea) {
  font: inherit;
  padding: .5rem .75rem;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: .5rem;
}

3-5. 表のヘッダ&フッタだけ(2か所を一気に)

.table :is(thead, tfoot) th {
  background: color-mix(in srgb, currentColor 8%, transparent);
}

4. 落とし穴と回避策

4-1. 特異性が強すぎて上書きできない…

  • 原因::is() は中の一番強い特異性を持つ
  • 解決:ベースは :where() に置き換える or セレクタを浅くする
/* 強すぎる例 */
.card :is(h2, h3, .title.strong) { ... }  /* .title.strong の特異性を引く */

/* 弱める(上書きしやすく) */
:where(.card) :where(h2, h3, .title) { ... }

4-2. ネストが深い・範囲が広い → パフォーマンス低下

  • 対策:スコープを限定(.article や .prose の配下に絞る)
  • 過剰最適化は不要だけど「ページ全体に重い選択」は避ける

4-3. ブラウザ対応は?

  • 2025年時点で 主要ブラウザは実用OK
  • どうしても古い環境を気にする場合は、素直に列挙も検討

5. 設計の型(これを真似すればOK)

型A:ベースは弱く(:where())、仕上げは強く(:is())

/* ベース(弱い) */
:where(.prose) :where(h1, h2, h3) { margin: .8em 0; color: #0f172a; }

/* 仕上げ(強い) */
.prose h1:is(.xl, .xxl) { font-size: clamp(2rem, 4vw, 3rem); }

型B:状態まとめ

.btn:is(:hover, :focus-visible, :active) { transform: translateY(-1px); }

型C:ターゲットまとめ + 条件追加

/* “入力っぽいもの全部”かつ“無効ではない” */
form :is(input, select, textarea):not(:disabled) { opacity: .95; }

6. コピペ用チートシート

/* まとめて:状態 */
.link:is(:hover, :focus-visible, [aria-current="page"]) { text-decoration: underline; }

/* まとめて:要素 */
.prose :is(h2, h3, h4) { margin-block: .8em; }

/* ベースを弱く置く */
:where(.prose) :where(h2, h3, h4) { margin-block: .8em; }

/* :has() と併用(テキストリンクだけ装飾) */
.article a:not(:has(img, svg)):is(:hover, :focus-visible) { text-decoration: underline; }

7. まとめ

  • :is() は「重複を畳みつつ、強さはそのまま
  • :where() は「重複を畳みつつ、強さゼロ(上書きしやすい)」
  • 設計の基本は ベース=:where()/仕上げ=:is()
  • :has() との併用で、短く・読みやすく・壊れにくいCSSに。