💳

display: contents の使い道と代替策

に公開

はじめに

この記事では、CSS のdisplay: contentsプロパティについて、確かに使えるけどこのユースケースは別の対応策があるかも、という知見をまとめています。

  • display: contents って何?
  • よくある使い方と代替案
  • 使う時の注意点

こんな人向け

  • HTML/CSS の基本が分かる人
  • Flexbox や Grid を使ったことがある人
  • React/Vue でコンポーネントを作っている人

display: contents とは?

一言で言うと

要素の「箱(ボックス)」を視覚的に消して、その子要素を親要素の直接の子として扱わせる CSS プロパティです。

言葉で表すと難しく聞こえますが、以下の図を見るとわかると思います。

通常の場合

┌──────┐  ┌────────────────────┐  ┌──────┐
│Item 1│  │ ┌──────┐  ┌──────┐ │  │Item 4│
│      │  │ │Item 2│  │Item 3│ │  │      │
└──────┘  │ └──────┘  └──────┘ │  └──────┘
          └────────────────────┘
          ↑ <div> などの wrapper が Flex アイテムになる

display: contents適用後

         - - - - - - - - - -
┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐
│Item 1│  │Item 2│  │Item 3│  │Item 4│
└──────┘  └──────┘  └──────┘  └──────┘
         - - - - - - - - - - ← でも <div> は存在
すべてが container の直接の子として扱われる

display: contents適用方法

.wrapper {
  display: contents;
}

よくある使用場面

実際に他の記事を参考にして、使えそうな場面と代替策を考察してみました。

1:レイアウトを崩すラッパー要素

React や Vue などのフレームワークで条件分岐のために追加したラッパー要素への使用です。以下はReactの場合の例です。

form.tsx
// ↓ 親要素の CSS で、子要素の並び方を変更させる ↓
<form className="form">
  <h1>ユーザー情報更新</h1>
  <InputField label="お名前" />
  <InputField label="メールアドレス" />
  <InputField label="電話番号" />
  <InputField label="住所" />
  <label>
    パスワードを変更する
    <input type="checkbox" />
  </label>
  {showPasswordField && (
    // ↓ この箇所に display: contents を付与 ↓
    <div className="contents">
      <InputField label="新しいパスワード" />
      <InputField label="新しいパスワード(確認)" />
    </div>
  )}
  <button type="submit">
    更新
  </button>
</form>
style.css
.form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.contents {
  display: contents;
}

display: contents がない場合とある場合では、親要素による間隔の有無に違いがあります。flex の影響を div で受け取っているからですね。

display: contents なし display: contents あり
before after
before after

display: contents を使用しない解決方法

今回の事例ですと、 div ではなく空の要素で囲えば、そもそもレイアウトに影響が出なくなります。

Reactの場合: <> または <React.Fragment>
Vueの場合: <template v-if="showPasswordField">

また、空の要素で実施したほうが、管理する DOM が少なく、パフォーマンス的にも優れていたりします

2:セマンティックな構造とレイアウトの衝突

次に、HTML の意味的構造(<section><ul>など)を保ちたいが、レイアウト上は不要な階層になってしまう部分の解消への使用です。

参考: [CSS]「display: contents;」がすごい便利!ラッパーを使った実装が大きく変わるこれからのテクニック

上記のようなレイアウトを作成するとき、 grid を使用すると実現できるようです。(ただし background は gap の位置だけ適用しないことは多分できないはず…?🤔)

index.html
<style>
  .grid {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto 1fr auto;
    grid-template-columns: repeat(2, 1fr);
    grid-column-gap: 20px;
  }
</style>

<div class="grid">
  <h2>This is a heading</h2>
  <p>...</p>
  <p>Footer stuff</p>

  <h2>This is a really really really super duper loooong heading</h2>
  <p>...</p>
  <p>Footer stuff</p>
</div>

上記はシンプルに div 要素の階層に並べていますが、SEO や可読性の観点だとそれぞれに <article> などで囲ってあげたほうが良いです。しかし、普通に実施するとレイアウトが崩れます。そこで登場するのが display: contents ですね。

index.html
<div class="grid">
  <article style="display: contents;">
    <h2>This is a heading</h2>
    <p>...</p>
    <p>Footer stuff</p>
  </article>
  <article style="display: contents;">
    <h2>This is a really really really super duper loooong heading</h2>
    <p>...</p>
    <p>Footer stuff</p>
  </article>
</div>

中に <article> を wrap していますが、display: contents のおかげでレイアウトが崩れずに適用されています。

display: contents を使用しない解決方法

そもそも全て container の .grid でレイアウトを構築する必要もなく、article アイテム側でレイアウトをコントロールすることができます。こうすることで、article アイテムを他で使いまわしすることができます。

index.html
<style>
  .container {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
    /* ここのgrid系のCSSが短縮されている */
  }
  /* 以下のCSSを追加 */
  .article {
    display: grid;
    grid-template-rows: 72px 1fr auto;
    background: #fff;
    padding: 20px;
  }
</style>

<div class="container">
  <article class="article">
    <h2>This is a heading</h2>
    <p>...</p>
    <p>Footer stuff</p>
  </article>
  <article class="article">
    <h2>This is a really really really super duper loooong heading</h2>
    <p>...</p>
    <p>Footer stuff</p>
  </article>
</div>

ただし、この実装の場合、元々テーブルっぽい位置調整であり、現在は heading を固定値にして同じ表現にしているので、grid や table の場合は完全な置き換えは難しそうです。

3. レスポンシブなカード

PC と SP でレイアウトを変更したいときに、SP 時のみ display: contents を付与すると、一応簡単に並びを変更できます。

index.html
<style>
  body {
    margin: 0;
    background: #f5f5f5;
  }

  .container {
    padding: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }

  .card {
    display: grid;
    grid-template-columns: 2fr 1fr;
  }
  .card-content {
    flex: 1;
    padding: 10px;
    background: #fff;
  }
  .card img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  @media (max-width: 768px) {
    .card {
      /* SP時に card を contents 要素にする */
      display: contents;
    }
  }
</style>
<div class="container">
  <div class="card">
    <div class="card-content">
      <h2>Hello World</h2>
      <p>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
      </p>
    </div>
    <img src="/images/sample.jpg" alt="" />
  </div>
  <div class="card">
    <div class="card-content">
      <h2>Hello World</h2>
      <p>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
      </p>
    </div>
    <img src="/images/sample.jpg" alt="" />
  </div>
</div>
PC SP

ただ、SP 表示で不自然に間隔が空いてしまっています。一見 background を .card に移動すれば解決しそうですが、実は解決しません。

実際 .card 側に background を適用しようとすると、背景が適用されません。

というのも、display: contents他のレイアウト関連の CSS を打ち消す効果があり、background はもちろん、paddingmargin などは無効化されます。

また、.container 側で横並びや間隔の変更を行うと、.card のレイアウトも大幅に崩れてしまいます

display: contents を使用しない解決方法

そこで今回のベストプラクティスとしては、SP 時の .card への display: contents はせず、その箇所に grid-template-columns: 1fr; を適用すること

そうすれば、 grid の標準の縦並びの機能を活かして実装できます。当然 .container 側で横並びや間隔の変更を行ってもレイアウト崩れの心配はありません。

grid 特有のプロパティを完全理解すると、display: contents を使用せずとも最適なスタイルができそうです。

その他の注意点

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

デフォルトでセマンティックロールやスタイルを持つ要素に適用すると、そのデフォルトの要素を失うため要注意です。

  • <a> <button> 要素が キーフォーカスできなくなる
  • <input> <select> 要素が何も表示されなくなる
  • <fieldset> 本来のデザインが適用されない(逆に必要ないなら打ち消すのもあり)
    • ただし <legend>display: content にすると a11y の観点で読み上げがされなくなる

ランドマークの喪失

<header><nav><main>などのランドマーク要素に適用すると、ナビゲーションに影響するっぽいです。

しかし chrome で検証したところ、今のところ治ってそうな感じはありますが、他のブラウザでは未検証なので念の為注意が必要です。

ボックスモデル関連のプロパティが効かない

よくある使用場面でも再現されましたが、background, border, padding, position などのプロパティは、display: contents を付与すると効かなくなります。

フォント関連の、子要素に継承されるようなコンテンツ領域のプロパティ(参考 - MDN)は contents 要素でも付与・継承されます。

擬似要素 (::before, ::after) も生成されない

display: contentsを適用すると、::before::after疑似要素も生成されません。これは <span> <a> などの inline 要素と同様ですね。

まとめ

display: contents の使える場面

  • Flexbox/Grid で不要な階層をスキップしたい時(セマンティック要素)
  • セマンティック要素調整(レスポンシブデザイン)で、かつ display: contents にして影響が出ないとき
  • 要素本来のデザイン(<fieldset> など)を打ち消したいとき

display: contents の使えるけど代替え策もある場面

  • React/Vue の条件分岐で作った div ラッパー → 空の要素で囲む
  • Flexbox/Grid で不要な階層をスキップしたい時 → アイテム側でレイアウト実装(完全置き換えは難しい)
  • セマンティック要素(<article><section>など)を保ちつつ grid レイアウトを調整したい時 → grid-template-columnsの利用もできる

display: contents の使わない方がいい場面

  • <button><a><input> などの操作できる要素
  • <nav><main> などの重要な意味を持つ要素
  • IE11 対応が必要な場合

display: contents の注意点

  • コンテンツ領域プロパティ以外のプロパティ(backgroundpaddingborder, position など)が効かなくなる
  • キーボード操作ができなくなる要素がある
  • 代替手段(Fragment や構造変更)も検討しよう

参考資料

MDN Web Docs

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

CSS Specification

https://www.w3.org/TR/css-display-3/#valdef-display-contents

関連記事

https://qiita.com/kana_zzzz/items/db2c9acca5a84c7848c2

https://zenn.dev/skmtko/articles/3570173649f8da

https://tomiwa-tech.co.jp/blog/css-display-contents/

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

Sun* Developers

Discussion