Back

JavaScript が不要になった最新の CSS 機能

JavaScript が不要になった最新の CSS 機能

長年にわたり、フロントエンド開発者はインタラクティブな UI パターンを実装するために JavaScript に頼ってきました。アコーディオンにはクリックハンドラーが必要でした。ツールチップには位置調整ライブラリが必要でした。レスポンシブコンポーネントには Resize Observer が必要でした。その時代は終わりつつあります。

最新の CSS は、状態に応じたスタイリング、コンポーネントレベルのブレークポイント、スクロールベースのエフェクト、ネイティブポップオーバーを、JavaScript を一行も書かずに実現できます。これらは実験的な機能ではありません。安定しており、広くサポートされ、2025年の本番環境で使用できる状態です。

重要なポイント

  • CSS の :has() セレクタは、子要素の状態に基づいて親要素をスタイリングできるため、フォームバリデーションのスタイリングやインタラクティブコンポーネントに JavaScript が不要になります。
  • コンテナクエリは、ビューポートではなくコンテナのサイズに応じてコンポーネントを調整でき、JavaScript の Resize Observer を置き換えます。
  • スクロール駆動アニメーションはコンポジタースレッドで実行されるため、Intersection Observer の代替手段よりもスムーズなパフォーマンスを提供します。
  • Popover API と <details name=""> 属性は、カスタムスクリプトなしでネイティブかつアクセシブルなツールチップ、メニュー、アコーディオンを提供します。

CSS :has() セレクタ:親要素の選択がついに実現

CSS の :has() セレクタは、開発者が何十年も不満を抱えてきた問題を解決します:子要素の状態に基づいて親要素をスタイリングすることです。

以前は、チェックボックスがチェックされたときにカードの外観を切り替えるには、JavaScript のイベントリスナーが必要でした。今では CSS が直接処理します:

.card:has(input:checked) {
  border-color: blue;
}

このパターンにより、JavaScript のカテゴリ全体が不要になります:

  • フォームバリデーションのスタイリング: :valid または :invalid な入力に基づいてコンテナをスタイリング
  • 状態駆動レイアウト: 子要素が空または存在する場合に親グリッドを変更
  • インタラクティブコンポーネント: 非表示の入力と :has() を使用してタブ、アコーディオン、トグルを構築

このセレクタは主要なブラウザすべてで動作します。古いブラウザのサポートには、@supports selector(:has(*)) でスタイルをラップし、ベースライン体験を提供します。

CSS コンテナクエリ:コンポーネントレベルのレスポンシブ対応

メディアクエリはビューポートサイズに応答します。CSS コンテナクエリはコンポーネントのコンテナサイズに応答します—これはレスポンシブレイアウトの構築方法における根本的な変化です。

.card-wrapper {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

これが重要な理由は、コンポーネントが異なるコンテキストに存在するためです。サイドバー内のカードは、メインコンテンツエリア内の同じカードとは異なる動作をします。コンテナクエリを使用すると、コンポーネントはブラウザウィンドウではなく、実際に利用可能なスペースに適応できます。

コンテナクエリ以前は、これを実現するには JavaScript の Resize Observer と手動でのクラス切り替えが必要でした。今では宣言的な CSS です。

スクロール駆動アニメーション:Intersection Observer は不要

スクロールトリガーアニメーションは従来、ライブラリのインポートまたは Intersection Observer コードの記述を意味していました。CSS スクロール駆動アニメーションは両方を置き換えます。

2つのタイムラインタイプがほとんどのユースケースに対応します:

スクロールタイムラインは、アニメーションの進行をスクロール位置に連動させます:

@keyframes grow {
  from { width: 0; }
  to { width: 100%; }
}

.progress-bar {
  animation: grow linear;
  animation-timeline: scroll();
}

ビュータイムラインは、要素がビューポートに入ったときにトリガーされます:

@keyframes fade-in {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 30%;
}

これらのアニメーションはコンポジタースレッドで実行されるため、JavaScript の代替手段よりもスムーズなパフォーマンスを提供します。常に @media (prefers-reduced-motion: reduce) でユーザーの設定を尊重し、アニメーションを無効化または簡素化してください。

Popover API:ネイティブツールチップとメニュー

アクセシブルなポップオーバーの構築には、従来、フォーカストラップの管理、外側クリック検出、Escape キー処理、z-index スタッキングが必要でした。Popover API はこれらすべてをネイティブに処理します。

<button popovertarget="menu">メニューを開く</button>
<div id="menu" popover>
  <p>メニューコンテンツ</p>
</div>

ブラウザは自動的に:

  • ポップオーバーをトップレイヤー(他のすべてのコンテンツの上)に配置
  • 外側をクリックまたは Escape を押すと閉じる
  • フォーカスを適切に管理
  • アクセシビリティのアナウンスを処理

ポップオーバーは CSS でスタイリングでき、@starting-style による入場アニメーションも含まれます。デフォルトの popover="auto" はユーザーが他の場所を操作すると閉じますが、popover="manual" は明示的な閉じる操作が必要です。

<details name=""> によるネイティブアコーディオン

<details> 要素は長年アコーディオンをサポートしてきました。name 属性は排他的な動作を追加します—一度に1つのパネルのみが開きます:

<details name="faq">
  <summary>最初の質問</summary>
  <p>回答コンテンツ</p>
</details>
<details name="faq">
  <summary>2番目の質問</summary>
  <p>回答コンテンツ</p>
</details>

JavaScript は不要です。完全なキーボードアクセシビリティ。スクリーンリーダーのサポートが組み込まれています。[open] 状態と ::marker 疑似要素をスタイリングして、デザインシステムに合わせます。

プログレッシブエンハンスメントは依然として重要

これらの機能は広くサポートされていますが、プログレッシブエンハンスメントは依然として良い慣行です。@supports を使用してフォールバックを提供します:

@supports not (container-type: inline-size) {
  /* メディアクエリを使用したフォールバックスタイル */
}

このアプローチにより、どこでもベースライン機能を確保しながら、サポートされている場所では改善された体験を提供します。

まとめ

JavaScript なしの最新の CSS は、JavaScript を完全に避けることではありません—適切なツールを選択することです。宣言的な CSS ソリューションは、実装が速く、保守が容易で、スクリプトによる同等のものよりもパフォーマンスが高いことがよくあります。

現在の JavaScript を監査することから始めましょう。状態に基づいてクラスを切り替えたり、ツールチップを配置したり、スクロール位置を監視している場合、CSS が今ではそれを処理できる可能性があります。ブラウザが重い作業を行います。任せましょう。

よくある質問

ここで取り上げた機能—:has()、コンテナクエリ、スクロール駆動アニメーション、Popover API—は、2025年時点で Chrome、Firefox、Safari、Edge を含むすべての主要ブラウザでサポートされています。スクロール駆動アニメーションは最もサポートが狭いため、常に caniuse.com で最新の互換性データを確認し、フォールバックには @supports を使用してください。

:has() セレクタは多くの状態ベースのスタイリングシナリオを処理しますが、限界があります。フォームの状態、子の存在、または兄弟の条件に基づくスタイリングには適しています。複雑な多段階ロジック、条件付きレンダリング、またはデータフェッチには、JavaScript が依然として必要です。:has() は視覚的な状態変更に使用し、アプリケーションロジックには使用しないでください。

CSS スクロール駆動アニメーションは通常、JavaScript の代替手段よりも優れたパフォーマンスを発揮します。これは、メインスレッドとは別のコンポジタースレッドで実行されるためです。これにより、レイアウトスラッシングやジャンクが防止されます。ただし、width や height などのレイアウト再計算をトリガーするプロパティをアニメーション化すると、パフォーマンスの問題が発生する可能性があります。最良の結果を得るには、transform と opacity を使用してください。

@supports ルールを使用して機能の可用性を検出し、代替スタイルを提供します。たとえば、@supports not (container-type: inline-size) を使用すると、メディアクエリのフォールバックを定義できます。JavaScript 依存のフォールバックの場合は、ポリフィルまたは代替実装を初期化する前に、スクリプトで機能サポートを確認してください。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay