💨

ボックスモデルを超えてゆけ!display: contents;の使い方について実例を交えながら解説

2025/03/20に公開

はじめに

CSS のボックスモデルは、レイアウトを構成する基本的な概念です。
しかし複雑なレイアウトを表現するのに不要なラッパー要素が増えてしまいがちで、マークアップが冗長になることがあります。
display: contents; はこの問題を解決するための CSS プロパティの一つで、余計なボックスを作らずにマークアップをシンプルに保つことができます。

本記事では、display: contents; の基本的な動作、実際のユースケース、考慮すべき注意点について述べていきたいと思います。

そもそもボックスモデルとは?

HTML 要素は、CSS のボックスモデルに基づいて描画されます。各要素は次のような四つの領域を持ちます。

  • コンテンツ領域:テキストや画像が配置される領域
  • パディング(padding):コンテンツとボーダーの間の余白
  • ボーダー(border):要素の枠線
  • マージン(margin):要素同士の間隔を確保するための外側の余白

display プロパティは、このボックスモデルの振る舞いを決定します。

代表的な display の値

  • block:ブロックレベル要素(div, p など)
  • inline:インライン要素(span, a など)
  • inline-block:インラインのままボックスを持つ要素
  • flex / grid:レイアウトコンテナとして機能する要素

では、display: contents; はどのように動作するのでしょうか?

display: contents; の仕組みと特徴

display: contents; の挙動

display: contents; を指定すると、その要素自体のボックス(レイアウト上の枠組み)が消え、子要素だけが親要素の直下にあるように振る舞います

例えば、次のような HTML があるとします。

<div class="wrapper">
  <div class="content">
    <p>テキスト1</p>
    <p>テキスト2</p>
  </div>
</div>

通常、.content はブロック要素として扱われます。
しかし、以下の CSS を適用すると

.content {
  display: contents;
}

.content のボックスは描画されず、p 要素は .wrapper の直下にあるかのように扱われます。

実例

例 1 ステッパー

会員登録画面や EC サイトのカート画面でよく見る以下のようなステッパーを作ってみます。

<ol>
  <li aria-current="step">1</li>
  <li>2</li>
  <li>3</li>
</ol>

マークアップとしてはこれで良さそうです。
次に CSS で装飾をしていきます。
現時点でバーを表現する要素はないのでdivで作ってみると、、、

<ol>
  <li aria-current="step">1</li>
  <div class="bar" aria-hidden></div>
  <li>2</li>
  <div class="bar" aria-hidden></div>
  <li>3</li>
</ol>

HTML がごちゃごちゃしてきました。
装飾のためにolの子要素にdivを配置するのは、美しく感じなかったので擬似要素で作ってみます。
また、デザインデータを確認すると各ステップとバー間には16pxの余白が設けられていました。

<ol>
  <li aria-current="step">1</li>
  <li>2</li>
  <li>3</li>
</ol>
ol {
  display: flex;
  gap: 16px;
}

li:not(:last-child)::after {
  margin-right: 16px;
  /* 以下にバー用の装飾を記述する */
}

これでも良さそうですが、ステップとバーが均等に並んでいるのに、余白の定義が複数箇所にあるのは違和感があります。
そこでdisplay: contents;を利用して改善してみます。

ol {
  display: flex;
  gap: 16px;
}

+ li {
+   display: contents;
+ }

li:not(:last-child)::after {
- margin-right: 16px;
  /* 以下にバー用の装飾を記述する */
}

liの子要素(ステップと擬似要素で作ったバー)がol要素の子要素として振る舞うようになりました。
そのためol要素に指定していたgapプロパティがliの子要素にも効くようになり、不要なmargin-rightを削減することができました。

例 2 Next.js App Router での利用

次にもう少し複雑なレイアウトを作ってみます。
Next.js の App Router を使っているとします。
以下のような全ページ共通のヘッダー、サイドバーがあるような管理画面を組んでみます。

// app/layout.tsx
import "./globals.css";

export default function AppLayout({ children }: PropsWithChildren) {
  return (
    <html>
      <body>
        <header />
        <aside />
        <main>{children}</main>
      </body>
    </html>
  );
}
/* app/globals.css */
body {
  display: grid;
  grid-template-areas:
    "header header"
    "aside main";
}

header {
  grid-area: header;
}

main {
  grid-area: main;
}

aside {
  grid-area: aside;
}

<header />, <aside />は全ページ共通で存在するのでapp/layout.tsxに配置しました。

// app/page.tsx
import styles from "./page.module.css";

export default function Page() {
  return (
    <>
      <Hero className={styles.hero} />
      <Content className={styles.content} />
    </>
  );
}

// app/blog/page.tsx
export default function BlogPage() {
  return (
    <>
      <BlogList />
    </>
  );
}

<main />配下の DOM はページ固有の UI を持つため、
app/page.tsx<Hero />, <Content />
app/blog/page.tsx<BlogList />を記述しました。

一見これで良さそうに思えますが、
768px 未満では/<Hero /><aside />の上に配置されるようなレイアウトに変化します。

これはディレクトリ構造上、ネストしていた<Hero />page.tsxを飛び出して、layout.tsxで定義した UI に影響を及ぼすような変化です。
これをどう実現するのか様々な方法はありますが、display: contentsorderプロパティで解決してみます。
CSS ファイルに以下を追加します。

/* app/globals.css */
@media (max-width: 768px) {
  body {
    display: flex;
    flex-direction: column;
  }
  main {
    display: contents;
  }
  header {
    order: 0;
  }
  aside {
    order: 2;
  }
}

/* app/page.module.css */
@media (max-width: 768px) {
  .hero {
    order: 1;
  }

  .content {
    order: 3;
  }
}

maindisplay: contents;を指定し、その子要素をbodyの子要素であるかのように振る舞わせます。
そしてbody直下の要素を縦に並べ、orderプロパティで好きな順番に並び替えることで、
共通 UI に影響を及ぼすようなファイルをまたぐレイアウトの変更を実現してみました。

アクセシビリティへの影響

display: contents;スクリーンリーダーで正しく解釈されない場合がある ため、特にナビゲーション要素やフォーム要素に適用する際は注意が必要です。

https://developer.mozilla.org/ja/docs/Web/CSS/display#display_contents

ブラウザーの互換性

IE を除く主要なブラウザではサポートされています
https://caniuse.com/mdn-css_properties_display_contents

まとめ

  • display: contents;を指定した要素の子要素は視覚上の枠組みを抜け出し、親の直下に配置されるように振る舞う。
  • CSS Grid や Flexbox と合わせて使うと有用な場面が多い。
  • アクセシビリティの影響には注意が必要。

CSS の進化により、より柔軟なレイアウトが可能になることが期待されます。特に display: contents; は、適切に使うことで HTML の構造をシンプルに保ちつつ、スタイルの自由度を高める強力なプロパティです。

今後もブラウザのアップデート情報をチェックしながら、適切な場面で活用していきましょう!

参考

https://coliss.com/articles/build-websites/operation/work/how-to-work-display-contents.html

GitHubで編集を提案

Discussion