Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

CSS: 2025年に押さえておきたい最新CSS機能13選(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました(原文で紹介されている機能は実際には13個です)。
項目ごとにMDNへのリンクも追加しています。

CSS: 2025年に押さえておきたい最新CSS機能13選(翻訳)

JavaScriptと同様に、CSSも(Rails)開発者の間であまり好かれていません。しかしJavaScriptにTurbo(古くはCoffeeScriptなど)があったように、CSSにもプリプロセッサやポストプロセッサ、抽象化の長い歴史があります(Tailwind CSSなど)。

このような階層化された機能の多くは、長い間とても必要とされていました。もう今では&(ネストセレクタ)が使えないCSSなど想像もできませんよね?

しかし2025年のCSSは急速に進化しつつあります(今この瞬間も進化しています)。spacer.gif画像でスペース調整を行ったり、カードコンポーネントの四隅にボーダーの丸みを模倣した画像を配置したりしていた涙ぐましい作業の時代はとっくに過ぎ去りました。

本記事では、(Tailwindを使っているなどの理由で)最近CSSにご無沙汰していた方を対象に、私がいくつかのプロジェクト(Rails DesignerSaaSなど)で使って役に立った新しいCSS機能の一部を紹介したいと思います。私はTailwind CSSを今も愛用していますが、現代のCSSを理解して実際に使っておくことは不可欠です。Web技術は(オープンな)標準の上に構築されており、アクセシビリティやメンテナンス性、そして将来性を確保し続けることが重要です。

🔗 1: min()max()clamp()

この3つは機能が重なっているので、ここでまとめて紹介します。

🔗 min()

/* 大画面表示でボタンが広がりすぎないようにする */
.pricing-button {
  width: min(300px, 90%);
}

min()は上限を設定する機能とお考えください。指定した複数の値のうち、小さい方の値を適用します。レスポンシブを維持しながら要素が大きくなりすぎないようにするのに便利です。
上の場合、90%を算出した結果が300pxより小さい場合は90%が適用され、超えた場合は300pxの値が使われます。

🔗 max()

/* 小さい画面でもテキストが読めるようにする */
.terms-container {
  font-size: max(16px, 1.2vw);
}

max()は上述のmin()に似ていますが、指定した複数の値のうち、大きい方の値を適用します。
モバイルデバイスで要素を縮小しすぎないようにしたい場合に最適です。

🔗 clamp()

/* 大きすぎず小さすぎない、完璧な流動的タイポグラフィを作成 */
.dashboard-title {
  font-size: clamp(1.5rem, 5vw, 3rem);
}

clamp()は上述のmin()max()の合わせ技です。以下の3つの値を指定します。

  1. 最小値
  2. 推奨値
  3. 最大値

🔗 2: コンテナクエリ

コンテナクエリは、要素を(ビューポートの幅ではなく)親コンテナのサイズに対してレスポンシブにできます(コンテナクエリ登場前は、サイズに応じて要素の外観を変更するにはメディアクエリビューポートの幅をベースにするしかありませんでした)。

/* ステップ1: 親をコンテナコンテキストとしてマーキングする */
.cards {
  container-type: inline-size;
}

/* ステップ2: cardコンポーネントのベーススタイル */
.card {
  padding: 0.75rem 1.25rem;
  background: white;
  border-radius: 1rem;
  /* デフォルトはスタックレイアウト */
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.card__value {
  font-size: 2rem;
}

/* ステップ3: コンテナの幅に応じてレイアウトを変更する */
@container (width > 200px) {
  .card {
    /* スペースが十分なら水平レイアウトに切り替える */
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

🔗 3: text-wrap: balancetext-wrap: pretty

訳注

この項目に限らず、CSSのテキスト関連プロパティは、英語などアルファベット語圏のテキスト(単語がスペース区切りで折り返し可能)が念頭に置かれていることがほとんどです。日本語はスペース分かち書きではないため、期待通りに動かないことがあります。

参考: 日本語におけるtext-wrapプロパティの運用

なお、韓国語の正書法にはスペース分かち書きがあるそうですが、単語単位というより、単語と助詞のような単位でスペース分かち書きされるようです。

参考: 韓国語【分かち書きのルール】スペースはどこに入れるのが正解!?|amilaniblog

複数行にわたるテキストの配分方法を制御して読みやすさを改善します。

  • balanceは、複数になった行の長さを揃えます(均等割)。

  • prettyは、最終行が1単語だけになる状態(タイポグラフィの専門家が言う孤立語(orphan))を回避します。

/* text-wrap: balance: 見出しに最適 */
.section-title {
  text-wrap: balance;
  /* 複数行の長さを揃える:
  "Welcome to our Platform"は
  "Welcome to our
   Platform"
   ではなく
  "Welcome to
   our Platform"
   になる*/
}

/* text-wrap: pretty: 本文パラグラフに最適 */
.card-description {
  text-wrap: pretty;
  /* 孤立語は改行を挿入して回避する
  "This is a long description about
   features"
  は以下になる
  "This is a long description
   about features" */
}

🔗 4: CSSの相対色構文

from構文を使うことで、任意のソースカラーからさまざまな形式の配色コンポーネントを抽出して変更できます。

.button {
  color: #0000FF; /* 原色のブルー */
  --brand: #FF0000; /* 原色のレッド */

  background: hsl(from currentColor h s l); /* h=240deg s=100% l=50%を抽出 */
  background: rgb(from var(--brand) r g b); /* r=255 g=0 b=0を抽出 */

  /* ブルー(h=240deg s=100% l=50%)を取り出してから変換する */
  background: hsl(from #0000FF calc(h + 60) calc(s * 0.8) calc(l * 1.2)); /* 結果はh=300deg s=80% l=60%(紫っぽい色)*/
}

🔗 5: マージンのブロックプロパティとインラインプロパティ

マージンとパディングの-blockプロパティと-inlineプロパティは、従来のmargin- top/right/bottom/leftpadding- top/right/bottom/leftと異なり、テキストの書字方向に対応しており、さまざまなwriting-mode(縦書きや横書きなど)やテキストを読むときの方向(LTRと、アラビア語などのRTL)に応じて表示を調整するために導入された論理プロパティです。

/* 従来のプロパティによる上下左右指定 */
.card {
  margin-top: 1rem;
  margin-bottom: 1rem;

  padding-right: 1.25rem;
  padding-left: 1.25rem;
}

/* -blockや-inlineによる最新のプロパティ */
.card {
  margin-block: 1rem; /* ブロック用のマージンを与える(通常は上下) */

  padding-inline: 1.25rem; /* インライン用のパディングを与える(通常は左右) */
}

/* -startや-endでさらに細かく制御 */
.card {
  margin-block-start: 1rem; /* ブロック方向の開始側だけにマージンを与える */
  margin-block-end: 2rem; /* ブロック方向の終了側だけにマージンを与える */

  padding-inline-start: 1rem; /* インライン方向の開始側だけにパディングを与える */
  padding-inline-end: 2rem; /* インライン方向の終了側だけにパディングを与える */
}

これらの新しいプロパティは、従来のtop/right/bottom/leftという物理的な方向指定に代わる論理プロパティです。CSSコードを削減するとともに、さまざまなwriting-modeやテキスト方向(LTRとRTL)に自動的に適応します。

🔗 6: @starting-style

新しい@starting-styleスタイルを使うと、display: noneの状態から開始される要素を適切にアニメーションします。@starting-styleは、要素の表示を開始したときの初期スタイルを定義します。

.modal {
  /* 非表示の状態 */
  display: none;
  opacity: 1;
}

.modal.open {
  display: block;
  /* 通常のtransitionが機能する */
  transition: opacity 300ms;
  opacity: 1;
}

/* モーダルが表示された直後の初期スタイルを定義する */
@starting-style {
  .modal.open {
    opacity: 0;
  }
}

🔗 7: :has()

:has()を使うと、従来のCSSでは不可能だった「どんな子要素を持っているかに基づいて親要素を選択する」ことが可能になります。


/* カード内にエラーメッセージが表示されている場合はカードのスタイルを変更する */
.card:has(.error-message) {
  border-color: red;
  background: rgb(255 0 0 / .05);
}

/* フォームの必須入力をグループ化する */
.form-group:has(input[required]) {
  /* 必須項目であることを"*"で示す */
  &::before {
    content: "*";
    color: red;
  }
}

🔗 8: メディアクエリの範囲構文

メディアクエリで最小値〜最大値の範囲をシンプルに指定する構文を使うことで、従来の冗長なmin-widthmax-widthによる指定を読みやすい比較演算子(<=>=など)に置き換えられます。

/* 従来の構文 */
@media (min-width: 768px) and (max-width: 1199px) {
  .card { width: 65ch; }
}

/* 新しい範囲構文 */
@media (768px <= width <= 1199px) {
  .card { width: 65ch; }
}

/* ブレークポイントでよく使われるパターン */
.container {
  /* 幅が768pxより小さい場合 */
  @media (width < 768px) {
    padding: 1rem;
  }

  /* 幅が1200px以上の場合 */
  @media (width >= 1200px) {
    max-width: 1140px;
  }

  /* 範囲指定によるブレークポイント */
  @media (480px <= width < 768px) {
    margin: 1.5rem;
  }
}

🔗 9: light-dark

light-darkは、ユーザーのカラースキームの設定(darkモードとlightモード)に応じて色の値を変更するための簡易記法です。そのためのメディアクエリをわざわざ作らずに済みます。

.card {
  /* 基本的な色の値 */
  background: light-dark(#f1f5f9, #0f172a);
  color: light-dark(#0f172a, #f1f5f9);

  /* 任意のカラーフォーマットを指定できる */
  border: 1px solid light-dark(rgb(226, 232, 240), rgb(51, 65, 85));
}

🔗 10: color-scheme

color-schemeは、コンポーネントでどのカラースキームをサポートするかをブラウザに指定できます。上述のlight-darkと組み合わせると便利です。これにより、スクロールバーやフォーム入力フィールドといったネイティブな要素が自動的にユーザーの好みに合うようになります。

/* グローバルレベルの指定(:rootが普通) */
:root {
  /* 2つのスキームをサポート(darkを先に書くとdarkが優先される) */
  color-scheme: dark light;
}

/* コンポーネントレベル(カードやモーダルなどのスキームをグローバルスキームから変えたい場合に便利) */
.themed-card {
  /* このカードではlightモードだけをサポートする */
  color-scheme: light;

  /* 内側のシステムUI要素をlightのままにする */
  input {
    /* 入力フィールドはdarkモードでもlightモードのままになる */
    border: 1px solid #ddd;
  }
}

/* darkモード専用のインターフェイスセクション */
.code-editor {
  /* 内部のシステムUIをすべてdarkモードにする */
  color-scheme: dark;
}

🔗 11: CSSネスティング

従来LessやSassなどのプリプロセッサで実現していたCSSネスティング(入れ子)機能がそのまま導入されました。親セレクタの内側に子セレクタを書く機能がCSSだけで直接書けるようになりました。コンポーネントとその子要素の関係をCSSで明確にするのに有用です(従来は、card__headerのようなBEM記法などを使って暗黙的に定義していました)。

.card {
  padding: 0.75rem 1.25rem;
  background-color: #fff;
  border-radius: 1rem;

  /* &記号で子を直接ネストする */
  & .header {
    font-size: 1.7rem;
  }

  /* ステートの変更もネスト可能 */
  &:hover {
    background: #f1f5f9;
  }

  /* 複数のセレクタもネスト可能 */
  & .title,
  & .subtitle {
    font-weight: 600;
  }

  /* メディアクエリもネスト可能 */
  @media (width > 768px) {
    padding: 1rem 1.375rem;
  }
}

🔗 12: 単位

CSSの単位には、絶対単位と相対単位があります。

cmmmin(インチ)などの絶対単位は、物理的な測定値が固定されており、CSSの初期から利用可能です。

px(ピクセル)は実は相対単位であり、表示デバイスによって大きさが異なることがあります。それ以外の多くの単位も相対単位であり、フォントやビューポートなどの要素に応じて大きさが異なることがあります。

CSS1のemはフォントサイズに応じて変わります。
CSS3では2012年頃にvhvwなどのビューポート単位が導入され、最近(2022年)になって導入されたdvhなどはモバイルブラウザでの動作が改善されています。

ただし、emexchなどのいくつかの相対単位は、利用場所によって計算結果が異なることがあり、font-sizeを元にした場合と、widthmarginを元にした場合とで振る舞いが変わる可能性もあります。

🔗 フォントベースの場合

em
親要素のフォントサイズを基準とする相対値
rem
root要素のフォントサイズを基準とする相対値
ex
現在のフォントのx-height(おおよそ小文字の"x"の高さ)を表す(この単位はCSS1のときからありますが、私が知ったのはつい最近でした)
ch
現在のフォントの"0"という文字の幅(CSS3から利用可能ですが見落とされがちです)
lh
その要素のline-heightと等しい

🔗 ビューポートベースの場合

vh
ビューポートの高さの1%
vw
ビューポートの幅の1%
vmin
ビューポートの幅と高さのうち小さい方の値の1%(CSS3からありますが、あまり使われていません)
vmax
ビューポートの幅と高さのうち大きい方の値の1%(CSS3からありますが、これもあまり使われていません)

🔗 最新のビューポート

dvh
モバイルブラウザのUI要素に合わせて調整された動的なビューポートの高さ
dvw
モバイルブラウザのUI要素に合わせて調整された動的なビューポートの幅
svh
ビューポートの小さい高さ(動的UI要素を考慮したときの潜在的な最小の高さ)
svw
ビューポートの小さい幅(動的UI要素を考慮したときの潜在的な最小の幅)
lvh
ビューポートの大きい高さ(動的UI要素を考慮したときの潜在的な最大の高さ)
lvw
ビューポートの大きい幅(動的UI要素を考慮したときの潜在的な最大の幅)

🔗 13: @layer

@layerは、Tailwind CSSを使ったことがある人にはお馴染みかもしれません。
@layerは、複数のスタイルグループを明示的に順序付けることで、詳細度(specificity)の衝突を管理します。以後のレイヤは、詳細度にかかわらず常に「勝つ」ようになるので、多くの開発者たちを混乱させイラつかせるカスケーディングを解消できます。

/* レイヤの順序を定義する(ここで指定する順序で優先順位が決まる) */
@layer reset, components, utilities;

/* resetレイヤ(優先順位最小) */
@layer reset {
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}

/* componentレイヤ(優先順位中) */
@layer components {
  .button {
    /* utilitiesレイヤの方が詳細度が小さい場合でも
    utilitiesレイヤのスタイルで上書きされる */
    padding: 0.5rem 1rem;
    background: blue;
  }
}

/* utilitiesレイヤ(優先順位最大) */
@layer utilities {
  .p-4 {
    /* これはcomponentレイヤのpaddingより優先される */
    padding: 1.25rem;
  }

  .bg-red {
    /* これはcomponentレイヤのbackgroundより優先される */
    background: red;
  }
}

本記事で紹介したプロパティは、私が最新プロジェクトで使っているプロパティのごく一部にすぎません。本記事が学びになった方は、ぜひお知らせください

関連記事

2025年のファビコンを極める: 必要なファイルはほぼ3つに減った!(翻訳)

CSS: z-indexの問題はisolationプロパティで解決できる(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。