CSS Container Queries でメディアクエリを卒業したら、コンポーネントの再利用性がかなり上がった話

ビューポート幅に依存した @media クエリを CSS Container Queries に置き換えたところ、コンポーネント単体でレスポンシブ対応が完結するようになり、管理がかなり楽になりました。

🙌 結論から

CSS Container Queries を使い始めてから、コンポーネント単体でレスポンシブ対応が完結するようになり、再利用するたびにメディアクエリを書き直す手間がかなり減りました!

コンテナのサイズに応じてスタイルを切り替える仕組みなので、「同じコンポーネントをサイドバーにも本文エリアにも配置したい」というケースで本領を発揮します。

Chrome・Firefox・Safari ともに対応済みなので、今すぐ本番でも導入できる状態です(^_^)

👀 これまでのメディアクエリの問題点

従来の @media クエリは、ビューポート(画面全体)の幅を基準にスタイルを切り替えます。

これが原因で、同じカードコンポーネントをサイドバーに配置すると「幅が狭くてレイアウトが崩れる」という問題が起きていました。

対処するために、配置場所ごとに追加のクラスを当てたり、ネストで無理やり上書きしたりと、管理が煩雑になりがちです(・_・;)

私のプロジェクトでも、記事カードコンポーネントを別ページで流用するたびにビューポートのブレークポイントが増えていき、「このクラスはどの配置用だったっけ?」という状態になっていました。

💡 Container Queries の基本

Container Queries は、コンテナ要素のサイズを基準にスタイルを切り替える CSS 機能です。

使い方はシンプルで、親要素に container-type を指定し、子要素のスタイルを @container で記述するだけです!

/* 親要素にコンテナを定義 */
.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

/* コンテナ幅に応じてスタイルを変える */
@container card (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

container-type: inline-size を指定すると、その要素が「コンテナ」として登録されます。

これだけで、このコンポーネントをどこに配置しても、親要素の幅に応じてスタイルが自動で切り替わります。

✅ 実際に書いてみた

私が最初に書き直したのは、ブログ記事のカードコンポーネントです。

メインカラムでは横並び・サイドバーでは縦積みにしたかったのですが、これまではビューポート幅で判定していたため、「画面幅は広いのにサイドバーにはめると崩れる」という問題が発生していました。

Container Queries に切り替えたコードはこのような形です。

.articles-grid {
  container-type: inline-size;
}

@container (min-width: 500px) {
  .article-card {
    grid-template-columns: 120px 1fr;
    align-items: center;
  }
}

@container (max-width: 499px) {
  .article-card {
    grid-template-columns: 1fr;
  }
}

この変更だけで、配置場所を変えるたびに追加 CSS を書く必要がなくなり、コンポーネントファイルを独立して管理できるようになりました(^o^)/

既存の @media クエリとも共存できるので、全部いっきに置き換える必要はありません。

😊 使ってみた感想

正直、もっと早く使い始めればよかったと思っています。

特に便利に感じたのは、デザインシステムを整理するタイミングです。コンポーネントが「どこに置かれるか」を意識せずに設計できるので、Storybook で単体確認したときの見た目が本番と乖離しにくくなりました。

一方で、すべてを Container Queries に置き換えるのが正解とは限らないとも感じています。

ページ全体のレイアウト(ナビゲーションの表示切り替えなど)はビューポート基準のほうが自然なことも多いです。

使い分けの感覚として、「コンポーネント内部の変化は Container Queries・ページ構造の変化はメディアクエリ」がしっくりきます。

🔀 論理演算子で条件を組み合わせる

カードコンポーネントを量産していくうちに、「幅400px以上、かつ高さも十分あるときだけ横並びにしたい」のように、条件を組み合わせたい場面が出てきました。

Container Queries は Media Queries と同じように and / or / not の論理演算子が使えます。

.card-wrapper {
  container-type: size; /* 幅と高さ両方を監視する */
}

/* 幅400px以上 かつ 高さ300px以上のときだけ横並び */
@container (min-width: 400px) and (min-height: 300px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

/* 幅が狭い、または高さが低いときは縦積みのまま */
@container not ((min-width: 400px) and (min-height: 300px)) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

ここで一つハマったのが、container-type: inline-size のままだと高さの情報を監視してくれないという点です。

高さも条件に含めたい場合は container-type: size を指定する必要があるんですが、これを設定すると要素の高さが「コンテナのサイズに依存しない固定値」を求められるようになり、レイアウトが崩れることがありました(・_・;)

結局私は、高さを条件に使うのはよほど必要なときだけに絞り、基本は inline-size(幅だけ監視)で組むという方針に落ち着きました。

or を使うパターンもよく使っていて、「タブレット幅相当、またはスマホの横向きのときだけ2カラムにする」のように、複数のブレークポイントをまとめて1つの @container に書けるのも意外と便利なポイントです。

@container (min-width: 480px) or (orientation: landscape) {
  .card {
    grid-template-columns: 1fr 1fr;
  }
}

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

🎉 まとめ

  • CSS Container Queries はコンテナ幅基準でスタイルを切り替える仕組み
  • 親要素に container-type: inline-size を設定し @container でスタイルを書く
  • 既存のメディアクエリと共存できるので段階的に導入できる
  • コンポーネントの再利用性が上がり、CSS 管理がかなり楽になる

コンポーネント指向で開発しているなら、ぜひ一度試してみてください🙌

※ ブラウザ対応状況や仕様は今後も更新される可能性があります。最新情報は MDN をご確認ください👀