TECHSCORE BLOG

マーケティングSaaS を提供するシナジーマーケティングのエンジニアブログです。

2025年 CSS トレンド – 開発現場で役立つ新機能 Part3

はじめに

Part1 ではコンテナクエリ、スクロールドリブンアニメーション、Subgrid、text-wrap を、Part2 では :has() セレクター、@starting-style、light-dark()、interpolate-size を紹介しました。

Part3 では、少し先を見据えた CSS の新機能を取り上げます。一部はブラウザサポートが発展途上ですが、プログレッシブエンハンスメントの考え方で導入すれば、今から活用を始められます。今回紹介するのは以下の4つです。

  • @scope
  • Anchor Positioning
  • field-sizing: content
  • View Transitions API

CSS の紹介

@scope

@scope は、スタイルの適用範囲を DOM 構造で限定できる機能です。BEM や CSS Modules を使わずとも、コンポーネント単位でスタイルを分離し、意図しないスタイル漏れを防げます。

実装手順

1. 基本構文を理解する

@scope ルールの中に、スコープの起点となるセレクターを指定します。スコープ内のスタイルは、その要素とその子孫にのみ適用されます。

@scope (.card) {
  /* .card 内でのみ有効 */
  .title {
    font-size: 1.25rem;
    font-weight: bold;
  }

  .content {
    color: #666;
  }
}
2. スコープの下限を設定する

to キーワードを使うと、スコープの終点を指定できます。入れ子になったコンポーネントへのスタイル漏れを防ぐのに有効です。

@scope (.card) to (.card-footer, .nested-card) {
  /* .card 内だが、.card-footer と .nested-card の中は除外 */
  p {
    line-height: 1.6;
  }
}
構文 説明
@scope (.a) .a 以下すべてに適用
@scope (.a) to (.b) .a 以下で .b に達するまで適用
@scope (.a) to (.b, .c) .a 以下で .b または .c に達するまで適用
3. :scope 疑似クラスを活用する

:scope 疑似クラスは、スコープのルート要素自体を指します。

@scope (.card) {
  :scope {
    /* .card 自体のスタイル */
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
  }

  .title {
    /* .card 内の .title */
    margin-top: 0;
  }
}

実用例

/* カードコンポーネント */
@scope (.card) {
  :scope {
    background: white;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 1.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }

  .title {
    font-size: 1.25rem;
    font-weight: 600;
    margin: 0 0 0.75rem;
  }

  .description {
    color: #666;
    line-height: 1.6;
    margin: 0;
  }

  .actions {
    margin-top: 1rem;
    display: flex;
    gap: 0.5rem;
  }
}

/* ネストしたコンポーネントの境界設定 */
@scope (.sidebar) to (.widget) {
  /* .sidebar 直下のスタイル、.widget 内部は別スコープ */
  a {
    color: #0066cc;
    text-decoration: none;
  }

  a:hover {
    text-decoration: underline;
  }
}

@scope (.widget) {
  :scope {
    background: #f5f5f5;
    padding: 1rem;
    border-radius: 4px;
  }

  /* .widget 内の a は独自スタイル */
  a {
    color: #333;
    font-weight: 500;
  }
}

/* サードパーティコンテンツの隔離 */
@scope (.user-content) to (.trusted-embed) {
  /* ユーザー投稿コンテンツ内のスタイル制限 */
  img {
    max-width: 100%;
    height: auto;
  }

  a {
    color: inherit;
    word-break: break-all;
  }

  /* 危険なスタイルの上書き */
  * {
    position: static !important;
    z-index: auto !important;
  }
}

@scope を使うと、CSS ファイル内でコンポーネントの境界が明確になります。グローバルな .title.content といった汎用的なクラス名でも、スコープ内でのみ有効になるため、命名規則に頼らずスタイルの衝突を防げます。


Anchor Positioning

Anchor Positioning は、ある要素を別の要素(アンカー)に対して相対的に配置できる機能です。JavaScript なしでツールチップやポップオーバーの位置決めを CSS で完結でき、画面端での自動フォールバックも可能です。

実装手順

1. アンカー要素を定義する

anchor-name プロパティで、基準となる要素に名前を付けます。名前はダッシュ付き識別子(-- で始まる)で指定します。

.trigger-button {
  anchor-name: --my-anchor;
}
2. 配置する要素でアンカーを参照する

position-anchor プロパティで参照先のアンカーを指定し、position: absolute で配置します。

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;
}
3. anchor() 関数で位置を計算する

anchor() 関数を使って、アンカー要素の各辺を基準に位置を指定します。

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;

  /* アンカーの上辺に配置 */
  bottom: anchor(top);

  /* アンカーの中央に揃える */
  left: anchor(center);
  translate: -50% 0;
}
anchor() の値 説明
anchor(top) アンカーの上辺
anchor(bottom) アンカーの下辺
anchor(left) アンカーの左辺
anchor(right) アンカーの右辺
anchor(center) アンカーの中央
anchor(start) 書字方向の開始辺
anchor(end) 書字方向の終了辺
4. フォールバック位置を設定する

position-try-fallbacks を使うと、優先位置に収まらない場合の代替位置を指定できます。

.tooltip {
  position: absolute;
  position-anchor: --my-anchor;

  /* 優先: 上に表示 */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0;
  margin-bottom: 8px;

  /* フォールバック: 下、右、左 の順で試行 */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
フォールバック値 説明
flip-block ブロック方向(上下)を反転
flip-inline インライン方向(左右)を反転
flip-block flip-inline 両方向を反転

実用例

/* ツールチップ */
.tooltip-trigger {
  anchor-name: --tooltip-anchor;
  position: relative;
}

.tooltip {
  position: absolute;
  position-anchor: --tooltip-anchor;

  /* デフォルト: 上に表示 */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% 0;
  margin-bottom: 8px;

  /* 画面端でのフォールバック */
  position-try-fallbacks: flip-block;

  /* スタイル */
  background: #333;
  color: white;
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
  font-size: 0.875rem;
  white-space: nowrap;

  /* 非表示状態 */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}

.tooltip-trigger:hover .tooltip,
.tooltip-trigger:focus .tooltip {
  opacity: 1;
}

/* ドロップダウンメニュー */
.dropdown-button {
  anchor-name: --dropdown-anchor;
}

.dropdown-menu {
  position: absolute;
  position-anchor: --dropdown-anchor;

  /* ボタンの下に配置 */
  top: anchor(bottom);
  left: anchor(left);
  margin-top: 4px;

  /* 画面下部でのフォールバック */
  position-try-fallbacks: flip-block;

  /* スタイル */
  background: white;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  min-width: anchor-size(width);
  padding: 0.5rem 0;

  /* 非表示状態 */
  display: none;
}

.dropdown-button[aria-expanded="true"] + .dropdown-menu {
  display: block;
}

.dropdown-item {
  padding: 0.5rem 1rem;
  cursor: pointer;
}

.dropdown-item:hover {
  background: #f5f5f5;
}

/* Popover API との組み合わせ */
.popover-trigger {
  anchor-name: --popover-anchor;
}

[popover] {
  position: absolute;
  position-anchor: --popover-anchor;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0;
  margin-top: 8px;

  position-try-fallbacks: flip-block, flip-inline;

  /* デフォルトスタイルのリセット */
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
<!-- ツールチップ HTML -->
<span class="tooltip-trigger">
  ホバーしてください
  <span class="tooltip">補足情報です</span>
</span>

<!-- Popover HTML -->
<button class="popover-trigger" popovertarget="my-popover">
  詳細を見る
</button>
<div id="my-popover" popover>
  <p>ポップオーバーのコンテンツです。</p>
</div>

Anchor Positioning を使うと、従来 JavaScript で計算していた位置決めロジックを CSS に移行できます。position-try-fallbacks による自動フォールバックにより、画面端でもコンテンツが見切れない UI を簡潔に実装できます。

注意: 2025年12月時点で Firefox は未対応です。プロダクションで使用する場合は、@supports でフォールバックを用意するか、ポリフィルの使用を検討してください。

/* フォールバック例 */
.tooltip {
  /* 基本のフォールバック */
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
}

@supports (anchor-name: --a) {
  .tooltip {
    /* Anchor Positioning 対応ブラウザ */
    position-anchor: --tooltip-anchor;
    bottom: anchor(top);
    left: anchor(center);
    translate: -50% 0;
    transform: none;
  }
}

field-sizing: content

field-sizing: content は、フォーム要素が入力内容に応じて自動的にサイズを調整する機能です。従来 JavaScript で実装していた <textarea> の自動伸縮を、CSS の1行で実現できます。

実装手順

1. field-sizing を適用する

対象のフォーム要素に field-sizing: content を指定するだけで、内容に応じたサイズ調整が有効になります。

textarea {
  field-sizing: content;
}
説明
fixed 従来どおり固定サイズ(デフォルト)
content 内容に応じてサイズを自動調整
2. サイズの上限・下限を設定する

min-width / max-widthmin-height / max-height でサイズの範囲を制限できます。

textarea {
  field-sizing: content;
  min-height: 3lh; /* 最低3行分 */
  max-height: 10lh; /* 最大10行分 */
}

input[type="text"] {
  field-sizing: content;
  min-width: 10ch; /* 最低10文字分 */
  max-width: 100%;
}

補足: lh 単位は要素の line-height を基準とした長さで、行数ベースの高さ指定に便利です。

3. 複数行テキストエリアの調整

<textarea> では、rows 属性と組み合わせて初期サイズを調整できます。

textarea {
  field-sizing: content;
  min-height: 3lh;
}
<!-- 初期状態で3行分の高さ -->
<textarea rows="3" placeholder="メッセージを入力..."></textarea>

実用例

/* 自動伸縮するテキストエリア */
.auto-textarea {
  field-sizing: content;
  min-height: 3lh;
  max-height: 20lh;
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font: inherit;
  line-height: 1.5;
  resize: vertical;
}

.auto-textarea:focus {
  outline: 2px solid #0066cc;
  outline-offset: -1px;
  border-color: #0066cc;
}

/* 内容に応じて伸びる入力欄 */
.flexible-input {
  field-sizing: content;
  min-width: 15ch;
  max-width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font: inherit;
}

/* タグ入力 UI */
.tag-input-container {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.5rem;
  background: #e9ecef;
  border-radius: 4px;
  font-size: 0.875rem;
}

.tag-input {
  field-sizing: content;
  min-width: 5ch;
  border: none;
  outline: none;
  font: inherit;
}

/* インライン編集 */
.editable-title {
  field-sizing: content;
  min-width: 10ch;
  font-size: 1.5rem;
  font-weight: bold;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: 0.25rem 0.5rem;
  background: transparent;
}

.editable-title:hover {
  border-color: #ddd;
}

.editable-title:focus {
  border-color: #0066cc;
  outline: none;
  background: white;
}
<!-- 自動伸縮テキストエリア -->
<textarea
  class="auto-textarea"
  placeholder="内容を入力すると自動的に高さが調整されます..."
></textarea>

<!-- タグ入力 -->
<div class="tag-input-container">
  <span class="tag">JavaScript<button>×</button></span>
  <span class="tag">CSS<button>×</button></span>
  <input type="text" class="tag-input" placeholder="タグを追加...">
</div>

<!-- インライン編集 -->
<input type="text" class="editable-title" value="クリックして編集">

field-sizing: content により、ユーザーの入力に合わせてフォーム要素が自然に伸縮します。従来は input イベントを監視して scrollHeight を取得し高さを再設定していましたが、CSS だけで完結するようになりました。

注意: 2025年12月時点で Firefox、Safari は未対応です。フォールバックとして固定サイズを指定しておくと安全です。

textarea {
  /* フォールバック */
  min-height: 100px;
  resize: vertical;
}

@supports (field-sizing: content) {
  textarea {
    field-sizing: content;
    min-height: 3lh;
    resize: vertical;
  }
}

View Transitions API

View Transitions API は、DOM の変更時にスムーズなアニメーションを自動で適用できる機能です。

たとえば以下のような演出を、複雑なライブラリなしで実現できます。

  • リスト項目を削除するとき、パッと消えるのではなくフェードアウト
  • ページ遷移時に、前のページと次のページがクロスフェード
  • カード一覧から詳細ページへ移動するとき、カード画像がそのまま拡大するような演出

実装手順

1. 最小限の例を試す(SPA)

まず、最もシンプルな例から始めましょう。document.startViewTransition() の中で DOM を変更するだけで、自動的にクロスフェードアニメーションが付きます。

<div id="box" style="width: 100px; height: 100px; background: coral;"></div>
<button id="btn">色を変える</button>

<script>
btn.addEventListener('click', () => {
  // startViewTransition で囲むだけ!
  document.startViewTransition(() => {
    box.style.background = box.style.background === 'coral' ? 'skyblue' : 'coral';
  });
});
</script>

これだけで、色がふわっと切り替わります。startViewTransition() を外すと、色がパッと瞬時に変わります。

2. アニメーションをカスタマイズする

デフォルトはクロスフェードですが、擬似要素にスタイルを適用することで、スライドやズームなど好みのアニメーションに変更できます。

View Transitions は以下の擬似要素を自動生成します。

擬似要素 説明
::view-transition-old(name) 変更前のスナップショット(消えていく側)
::view-transition-new(name) 変更後のスナップショット(現れる側)
/* アニメーション時間を変更 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.5s;
}

/* スライドインに変更 */
@keyframes slide-in {
  from { transform: translateX(100%); }
}

@keyframes slide-out {
  to { transform: translateX(-100%); }
}

::view-transition-old(root) {
  animation: slide-out 0.3s ease-out;
}

::view-transition-new(root) {
  animation: slide-in 0.3s ease-out;
}

3. 特定の要素だけ別のアニメーションにする

デフォルトでは、ページ全体が1つのまとまりとしてクロスフェードします。特定の要素だけ別のアニメーションにしたい場合は、view-transition-name で名前を付けます。

例:ヒーロー画像だけゆっくりアニメーション

<div class="page">
  <img class="hero-image" src="photo.jpg">
  <h1>タイトル</h1>
  <p>本文テキスト...</p>
</div>
/* この画像に「hero」という名前を付ける */
.hero-image {
  view-transition-name: hero;
}

/* hero だけ専用のアニメーション設定 */
::view-transition-old(hero),
::view-transition-new(hero) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}

結果: ヒーロー画像は 0.4秒かけてゆっくり切り替わり、それ以外は通常速度でクロスフェードします。

注意:名前はページ内で一意にする

view-transition-name は重複できません。同じ名前が複数あるとエラーになります。

/* ❌ ダメな例:全カードが同じ名前 */
.card {
  view-transition-name: card;
}

リストアイテムなど複数の要素に付ける場合は、それぞれ別の名前にします。

<!-- ✅ 良い例:それぞれ別の名前 -->
<div class="card" style="view-transition-name: card-1">...</div>
<div class="card" style="view-transition-name: card-2">...</div>
<div class="card" style="view-transition-name: card-3">...</div>
/* カード共通のアニメーション設定は view-transition-class で */
/* または JavaScript で動的に名前を付ける */

SPA と MPA の使い分け

種類 使う場面 実装方法
SPA React / Vue などで DOM を動的に更新 document.startViewTransition()
MPA 通常のリンクでページ遷移 @view-transition { navigation: auto; }

実用例:カードリストのアニメーション

/* カードごとに名前を付ける(JavaScript で動的に設定) */
.card-item {
  view-transition-name: var(--card-name);
}

/* 追加時のアニメーション */
@keyframes card-enter {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
}

::view-transition-new(card):only-child {
  animation: card-enter 0.3s ease-out;
}

/* 削除時のアニメーション */
@keyframes card-exit {
  to {
    opacity: 0;
    transform: scale(0.8);
  }
}

::view-transition-old(card):only-child {
  animation: card-exit 0.3s ease-in;
}

/* モーション軽減設定を尊重 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
function addCard(data) {
  document.startViewTransition(() => {
    const card = createCardElement(data);
    card.style.setProperty('--card-name', `card-${data.id}`);
    cardList.appendChild(card);
  });
}

function removeCard(id) {
  document.startViewTransition(() => {
    document.querySelector(`[data-id="${id}"]`).remove();
  });
}

ブラウザサポートと注意点

SPA(document.startViewTransition() Chrome、Edge、Safari、Firefox 144+ で対応。広くサポートされています。

MPA(@view-transition でのページ間遷移) Chrome、Edge、Safari 18.2+ で対応。Firefox は開発中です。

非対応ブラウザでもエラーにはならず、単にアニメーションなしで動作します。フィーチャーディテクションを入れておくとより安全です。

function updateDOM() {
  // DOM を変更する処理
}

// 対応ブラウザならアニメーション付き、非対応なら即時更新
if (document.startViewTransition) {
  document.startViewTransition(updateDOM);
} else {
  updateDOM();
}

まとめ

Part3 では、少し先を見据えた CSS の新機能を紹介しました。

  • @scope: スタイルの適用範囲を DOM 構造で限定でき、コンポーネント単位のスタイル分離が CSS だけで実現できる
  • Anchor Positioning: ツールチップやポップオーバーの位置決めを CSS で完結でき、画面端での自動フォールバックも可能
  • field-sizing: content: フォーム要素が内容に応じて自動でサイズ調整され、JavaScript での高さ計算が不要になる
  • View Transitions API: DOM の変更やページ遷移時にスムーズなアニメーションを適用でき、アプリの状態変化を視覚的に表現できる

これらの機能はブラウザサポートが発展途上のものもありますが、プログレッシブエンハンスメントの考え方で導入すれば、対応ブラウザではリッチな体験を、非対応ブラウザでも基本機能を提供できます。@supports によるフィーチャーディテクションを活用し、段階的に導入を進めていきましょう。

全3回にわたって、2025年の開発現場で役立つ CSS の新機能を紹介してきました。CSS の進化は続いており、JavaScript に頼っていた処理を CSS に移行できる場面がますます増えています。新しい機能を積極的に試しながら、よりシンプルで保守しやすいコードを目指していきましょう!

hiraokuのプロフィール画像
hiraoku

暇があったらクライミングしているフロントエンドエンジニアです。

シナジーマーケティング株式会社では一緒に働く仲間を募集しています。