CSSのdisplayプロパティは、文字通り要素の表示形式を指定する。フローレイアウト、フレックスボックスレイアウト、グリッドレイアウトなどが花形というか、知名度が高いかと思う。

このほかにも、テーブルレイアウト、リストアイテム様レイアウト、ルビレイアウトなどを実現できる。実際、<table>タグや<li>タグ、<ruby>タグ、およびそれらに関連するタグのdisplayプロパティには、各タグが持つ役割に対応する値がデフォルトで指定されている。

それらの値に関する基本知識を踏まえつつ、簡単なデモで挙動を確かめてみたい。

基本的な2系統の属性値

まず理解しておきたいのは、displayはその要素の表示形式と、その要素の子要素の表示形式という、大きく2種類の値を扱う点である。前者は"Outer display type"、後者は"Inner display type"と表現される。

タイプ 対応する属性値の例
Outer display type block / inline
Inner display type flow / flow-root / flex / grid / table / ruby

こう見ると、両方同時に指定できそうである。実際、displayプロパティには複数の属性値を指定できる。ただ一般的でなく、W3Cの勧告でも、試用期間中に仕様から削除されるかもわかりません、みたいな扱いをされている。

かと思えば普通にMDNのページがあったりする。わからない。わからないので見なかったことにして、このページでは値1つを指定する形を貫こうと思う。まずは基本から一歩一歩学ぶのだ。

"Inner display type"だけを指定すると、デフォルトでは"Outer display type"はblockとして処理される(<ruby>タグは例外で、inlineとして処理される)。

反対に"Outer display type"だけを指定すると、デフォルトでは"Inner display type"はflowとして処理される。

ちなみにフローレイアウトは、行と段落(=インラインとブロック)を下地とする古き良き基本のレイアウトである。

ともかく、CSSでは適用するレイアウトを内側と外側の2つに分けることで、異なるレイアウトモデルをうまく共存させている。

レイアウトの種類

CSSで使えるレイアウト、つまりdisplayプロパティで実現できるレイアウトには、以下のような種類がある。

レイアウトの種類 概要
フローレイアウト 行と段落(インラインとブロック)を下地とする基本的なレイアウト。
関連する属性値:display: block; / display: inline; / display: flow; / display: flow-root;
フレックスボックスレイアウト 要素を1次元(縦・あるいは横)のコンテナに収めて整列するレイアウト。
関連する属性値:display: flex;
グリッドレイアウト 要素を2次元(網目状)のコンテナに収めて整列するレイアウト。
関連する属性値:display: grid;
テーブルレイアウト 読んだまま。デフォルトでは<table>タグ(およびその関連タグ)に適用されている。
関連する属性値:display: table; / display: table-*; (※table-接頭辞を持つ値。)
ルビレイアウト デフォルトでは<ruby>タグ(およびその関連タグ)に適用されている。
関連する属性値:display: ruby; / display: ruby-*; (※ruby-接頭辞を持つ値。)
リストアイテム様レイアウト デフォルトでは<li>タグに適用されている。
関連する属性値:display: list-item;

リストアイテム様レイアウトに関しては、適切な単語が見当たらなかったため勝手に命名した。display: list-item;は、OuterでもInnerでもなく、::marker擬似要素を持つボックスを生成するプロパティ値である。

フローレイアウト1

フローレイアウトは、前後で改行する要素としない要素(ブロック要素とインライン要素)で、文章の方向に沿って流れるように要素を組み合わせていく基本的なレイアウトである。

以下の2つの値で、フローレイアウト内での要素の表示形式を指定する。つまり要素の外側に対して、その要素をどう表示するかを指定する。

displayプロパティの値 概要
block 要素の前後で改行する(=ブロックボックス)。
inline 前後で改行しない(=インラインボックス)。
inline-block "Outer display type"をインラインにしたブロックボックス。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #parent {
        background-color: gainsboro;
        margin:1em;
      }
      .child {
        display: block;
        background-color: gold;
        border: dashed 1px black;
        margin:1em;
        width: 100px;
        height: 100px;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, p, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          { id: "parent" },
          p({ id: "child1", class: "child" }, "Child1"),
          p({ id: "child2", class: "child" }, "Child2")
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = "",
        values = [],
        defaultValue = "",
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: "radio",
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: "child1",
          targetPropName: "display",
          groupName: "Child1のdisplayプロパティ",
          defaultValue: "block",
          values: ["block", "inline", "inline-block"],
        }),
        PropsToggler({
          targetId: "child2",
          targetPropName: "display",
          groupName: "Child2のdisplayプロパティ",
          defaultValue: "block",
          values: ["block", "inline", "inline-block"],
        }),
        SampleBox()
      );
    </script>
  </body>
</html>

display:inline;にすると幅と高さが失われ、ゴリゴリくんからフニャフニャくんになることがわかる。

フローレイアウト2

flowflow-rootは、子要素に対してフローレイアウトを指定する。

displayプロパティの値 概要
flow 中の要素をフローレイアウトにする。
flow-root 中の要素をフローレイアウトにし、新しいBFCを生成する。

BFC(Block formatting context=ブロック整形コンテキスト)というのは、フローレイアウトの1つの単位である。例えばmarginの相殺やfloatの挙動は、BFC単位で計算される。

親要素にdisplay: flow-root;を指定すると、その子要素たちのために新たなBFCが確立される。親要素とのmarginの相殺は起こらないし、floatを指定した要素も高さを持つ。

html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #parent {
        background-color: gainsboro;
        margin:1em;
      }
      .child {
        display: block;
        background-color: gold;
        border: dashed 1px black;
        margin:1em;
        width: 100px;
        height: 100px;
      }
      .floated {
        float: left;
        background-color: coral;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, p, fieldset, legend, input, label } =
        van.tags;

      const SampleBox = () => {
        return div(
          { id: 'parent' },
          p({ id: 'child1', class: 'child' }, 'Child1'),
          p({ id: 'child2', class: 'child floated' }, 'Child2\n(floated)')
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = "",
        values = [],
        defaultValue = "",
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: "radio",
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId:'parent',
          targetPropName: 'display',
          groupName:'コンテナ要素のdisplayプロパティ',
          defaultValue: 'block',
          values: ['block','inline','flow', 'flow-root'],
        }),
        PropsToggler({
          targetId:'child1',
          targetPropName: 'display',
          groupName:'Child1のdisplayプロパティ',
          defaultValue: 'block',
          values: ['block', 'inline'],
        }),
        SampleBox,
      );
    </script>
  </body>
</html>

display: block;、あるいはdisplay: inline;を設定すると、その要素の"Inner display"はdisplay: flow;として処理される。また、display: flow;を設定すると、その要素の"Outer display"はdisplay: block;として扱われる。

そのため上記のデモではdisplay: flow;どのような働きをするのかよくわからない。というか、どういうデモを作ればいいのかわからなかった。たぶん、複数の値を組み合わせて指定する際に真価を発揮するのであろう(投げやり)。

display: flow-root;に関しては、指定すると子要素のmarginが相殺されなくなり、floatを指定した要素の高さが確保されていることがわかる。

フレックスボックスレイアウト

flexは、子要素に対して縦あるいは横一列のレイアウト(フレックスボックスレイアウト)を指定する。

displayプロパティの値 概要
flex 子要素たちを縦あるいは横一列に整列する。
inline-flex "Outer display type"をインラインにしつつ、フレックスボックスモデルレイアウトを維持。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #parent {
        background-color: gainsboro;
        margin-top: 1em;
      }
      .child {
        margin: 1em;
        background-color: gold;
        border: dashed 1px black;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          { id: "parent" },
          div({ id: "child1", class: "child" }, "親に`display: flex;`を"),
          div({ id: "child2", class: "child " }, "指定すると、"),
          div({ id: "child3", class: "child " }, "子の`display:block;`は無力。")
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = "",
        values = [],
        defaultValue = "",
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: "radio",
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: "parent",
          targetPropName: "display",
          groupName: "コンテナ要素のdisplayプロパティ",
          defaultValue: "block",
          values: ["block", "flex", "inline-flex"],
        }),
        SampleBox
      );
    </script>
  </body>
</html>

親要素にdisplay: flex;を指定すると、子要素にdisplay: block;を指定していても、その要素はフレックスアイテムとして解釈される(=フレックス整形コンテキストに組み込まれる)。要素の前後で改行し、画面幅いっぱいに広がる、というようなブロック要素に期待できる整形はなされない。

なお、整列の方向や要素同士の間隔、折り返し、順序の指定などは、フレックスレイアウト専用の属性値を使って行う。本記事の主眼はdisplayプロパティであるため、ここではdisplay: flex;の挙動確認のみに留める。

グリッドレイアウト

gridは、子要素に対しての2次元的な網目状のレイアウト(グリッドスレイアウト)を指定する。

displayプロパティの値 概要
grid 子要素たちを網目状に整列する。
inline-grid "Outer display type"をインラインにしつつ、グリッドレイアウトを維持。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #parent {
        background-color: gainsboro;
        margin-top: 1em;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr 1fr;
      }
      .child {
        margin: 1em;
        background-color: gold;
        border: dashed 1px black;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          { id: "parent" },
          div({ id: "child1", class: "child" }, "親に`display: grid;`を"),
          div({ id: "child2", class: "child " }, "指定しても、"),
          div(
            { id: "child3", class: "child " },
            "子の`display:block;`はやっぱり無力。"
          ),
          div({ id: "child4", class: "child " }, "無力は言い過ぎか。"),
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = "",
        values = [],
        defaultValue = "",
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: "radio",
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: "parent",
          targetPropName: "display",
          groupName: "コンテナ要素のdisplayプロパティ",
          defaultValue: "block",
          values: ["block", "grid", "inline-grid"],
        }),
        SampleBox
      );
    </script>
  </body>
</html>

display: flex;と同じように、親要素にdisplay: grid;を指定すると、その子要素はグリッドアイテムとして解釈される(=グリッド整形コンテキストに組み込まれる)。

こちらも整列の詳細はグリッドレイアウト専用のプロパティで指定する。ここではグリッド感を伝えるために、display: grid;と合わせてgrid-template-columnsgrid-template-columnsという最小限のプロパティを指定している。

blockからgridに変更すると、あらかじめ指定おいたgrid-template-columnsgrid-template-columnsのプロパティが適用されることがわかる。

テーブルレイアウト

tableは、子要素に対してテーブルレイアウトを指定する。

フレックスモデルレイアウトやグリッドレイアウトは、親要素のdisplayプロパティにレイアウトの種類を指定し、子要素の整形は専用のプロパティを用いる。

一方、テーブルレイアウトや後述のルビレイアウトは、子要素のdisplayプロパティを使って整形を行う。この違いは、たぶん実装された時期のせい。

displayプロパティの値 概要
table 子要素に対してテーブルレイアウトを指定する。具体的な構造は子要素のdisplayに指定された値による。
inline-table "Outer display type"をインラインにしつつ、テーブルレイアウトを維持。
table-header-group 指定した要素を<thead>タグのように扱う。
table-column-group 指定した要素を<colgroup>タグのように扱う。
table-column 指定した要素を<col>タグのように扱う。
table-caption 指定した要素を<caption>タグのように扱う。
table-row-group 指定した要素を<tbody>タグのように扱う。
table-row 指定した要素を<tr>タグのように扱う。
table-cell 指定した要素を<td>タグのように扱う。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      .table {
        display: table;
        margin: 1em;
        width:400px;
      }
      .table-row-group {
        display: table-row-group;
      }
      .table-header-group {
        display: table-header-group;
      }
      .table-footer-group {
        display: table-footer-group;
      }
      .table-row {
        display: table-row;
      }
      .table-cell {
        display: table-cell;
      }
      .table-column-group {
        display: table-column-group;
      }
      .table-column {
        display: table-column;
      }
      .table-caption {
        display: table-caption;
        caption-side: top;
      }
      .table,
      .table-cell {
        border: solid 1px black;
        border-collapse: collapse;
        padding: 0.5em;
      }
      .bg-gold {
        background-color: gold;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          { class: 'table', id: 'table' },
          div({ class: 'table-caption', id: 'caption' }, 'caption'),
          div(
            { class: 'table-column-group', id: 'colgroup' },
            div({ class: 'table-column bg-gold', id: 'col1' }),
            div({ class: 'table-column', id: 'col2' })
          ),
          div(
            { class: 'table-header-group', id: 'thead' },
            div(
              { class: 'table-row', id: 'tr1' },
              div({ class: 'table-cell', id: 'th1' }, 'th1'),
              div({ class: 'table-cell', id: 'th2' }, 'th2')
            )
          ),
          div(
            { class: 'table-row-group', id: 'tbody' },
            div(
              { class: 'table-row', id: 'tr2' },
              div({ class: 'table-cell', id: 'td1' }, 'td1'),
              div({ class: 'table-cell', id: 'td2' }, 'td2')
            )
          ),
          div(
            { class: 'table-footer-group', id: 'tfoot' },
            div(
              { class: 'table-row', id: 'tr3' },
              div({ class: 'table-cell', id: 'td3' }, 'td3'),
              div({ class: 'table-cell', id: 'td4' }, 'td4')
            )
          )
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = '',
        values = [],
        defaultValue = '',
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: 'radio',
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: 'table',
          targetPropName: 'display',
          groupName: 'コンテナ要素のdisplayプロパティ',
          defaultValue: 'table',
          values: ['block', 'inline', 'table', 'inline-table'],
        }),
        PropsToggler({
          targetId: 'caption',
          targetPropName: 'display',
          groupName: '.captionのdisplayプロパティ',
          defaultValue: 'table-caption',
          values: ['block', 'inline', 'table-caption'],
        }),
        PropsToggler({
          targetId: 'colgroup',
          targetPropName: 'display',
          groupName: '.table-column-groupのdisplayプロパティ',
          defaultValue: 'table-column-group',
          values: ['block', 'inline', 'table-column-group'],
        }),
        PropsToggler({
          targetId: 'col1',
          targetPropName: 'display',
          groupName: '.table-column(1つ目)のdisplayプロパティ',
          defaultValue: 'table-column',
          values: ['block', 'inline', 'table-column'],
        }),
        SampleBox,
        PropsToggler({
          targetId: 'thead',
          targetPropName: 'display',
          groupName: '.table-header-groupのdisplayプロパティ',
          defaultValue: 'table-header-group',
          values: ['block', 'inline', 'table-header-group'],
        }),
        PropsToggler({
          targetId: 'th1',
          targetPropName: 'display',
          groupName: '.table-cell(1つ目)のdisplayプロパティ',
          defaultValue: 'table-cell',
          values: ['block', 'inline', 'table-cell'],
        }),
        PropsToggler({
          targetId: 'tbody',
          targetPropName: 'display',
          groupName: '.table-row-groupのdisplayプロパティ',
          defaultValue: 'table-row-group',
          values: ['block', 'inline', 'table-row-group'],
        }),
        PropsToggler({
          targetId: 'tfoot',
          targetPropName: 'display',
          groupName: '.table-footer-groupのdisplayプロパティ',
          defaultValue: 'table-footer-group',
          values: ['block', 'inline', 'table-footer-group'],
        })
      );
    </script>
  </body>
</html>

tableをはじめ、table-*系のプロパティ値は、それぞれが適用された要素をテーブルレイアウトに組み込む。そのため、先のflexと違い、コンテナ要素のdisplayプロパティに関係なしに、各プロパティ値に応じた振る舞いをする。

ルビレイアウト

rubyは、子要素に対してルビレイアウトを指定する。テーブル要素と同じように、具体的な構造は子要素のdisplayに指定された値による。

仕様では以下の5つが紹介されているが、MDNを見てみると<rb><rtc>は廃止マークがついている。覚えなくていいなら覚えないぞ。

displayプロパティの値 概要
ruby 子要素に対してルビレイアウトを指定する。
ruby-text 指定した要素を<rt>タグのように扱う。
ruby-base 指定した要素を<rb>タグのように扱う(非推奨?)。
ruby-base-container 複数の<rb>タグを包含する際に使う(非推奨?)。
ruby-text-container 指定した要素を<rtc>タグのように扱う(非推奨?)。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      .ruby {
        border:dashed 1px black;
        background-color:gold;
        display: ruby;
      }
      .ruby-text {
        display: ruby-text;
        font-size: 0.5em;
      }
      div:nth-last-child(2){
        margin-bottom:1em;
      }

    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          { class: 'ruby', id: 'ruby' },'BYBYRUBYBYRUBYBYBYRUBY',
          div({ class: 'ruby-text', id: 'rt' }, 'びびるびびるびびるび')
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = '',
        values = [],
        defaultValue = '',
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: 'radio',
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: 'ruby',
          targetPropName: 'display',
          groupName: 'コンテナ要素のdisplayプロパティ',
          defaultValue: 'ruby',
          values: ['block', 'inline', 'ruby'],
        }),
        PropsToggler({
          targetId: 'rt',
          targetPropName: 'display',
          groupName: '.ruby-textのdisplayプロパティ',
          defaultValue: 'ruby-text',
          values: ['block', 'inline', 'ruby-text'],
        }),
        SampleBox
      );
    </script>
  </body>
</html>

なお、display: ruby;を指定するとその要素の"Outer display type"はinlineになる。ブロックにしたいときは、display: block ruby;のように複数の値を指定する。やっぱり複数の値を指定できるのもスタンダードなのか? おいおい調べてみたい。

リストアイテム様レイアウト

list-itemは、::marker擬似要素のついたボックスを生成する。コンテナ要素がなくとも、これを指定した要素はリストアイテムとして扱われる(ただしコンテナ要素がないと、<ol>タグ風にナンバリングしたいときに困る。何かに収めておいた方が無難だ)。

displayプロパティの値 概要
list-item 要素をリストアイテムにする(指定した要素を<li>タグのように扱う。 )。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      .list-item {
        background-color: gold;
        border: dashed 1px black;
        display: list-item;
        list-style-type:disc;
        list-style-position: inside;
        margin-top: 1em;
        margin-left:1em;
      }
      .list-item--ol{
        list-style-type:decimal;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return (div({class:'container'},
          div(
            div({ class: 'list-item', id: 'li1' }, 'li1'),
            div({ class: 'list-item list-item--ol', id: 'li2' }, 'li2')
          ),
        ));
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = '',
        values = [],
        defaultValue = '',
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: 'radio',
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: 'li1',
          targetPropName: 'display',
          groupName: '.list-item(li1)のdisplayプロパティ',
          defaultValue: 'list-item',
          values: ['block', 'inline', 'list-item'],
        }),
        PropsToggler({
          targetId: 'li2',
          targetPropName: 'display',
          groupName: '.list-item(li2)のdisplayプロパティ',
          defaultValue: 'list-item',
          values: ['block', 'inline', 'list-item'],
        }),
        SampleBox
      );
    </script>
  </body>
</html>

display: list-item;を指定すると、その要素はデフォルトでは<ul>タグのリストアイテムとして振る舞う。

また、list-style-typedecimalを指定すると、<ol>タグ的に各アイテムがナンバリングされる。ナンバリングは、同じコンテナに含まれるdisplay: list-item;の数に基づいて行われる。

なので、上記デモのli1blockあるいはinlineにすると、li2のナンバリングが2から1になる。

その他の属性値

レイアウトを指定する属性値のほかに、要素をなきものにするやつがある

要素をなきものにするやつ

以下の2つの属性値は、それぞれ違う形で指定した要素をないものとして扱う。

displayプロパティの値 概要
contents その要素のボックスだけがCSSの処理から除外される。テキストも子要素も擬似要素も、ボックスのコンテンツはそのまま残される。
none その要素を含めて、擬似要素も子要素もまとめてないものとして扱われる。
html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #contents,
      #none {
        border: dashed 1px black;
        background-color: gold;
        margin: 1em;
        padding: 1em;
      }
      #contents:before,
      #none:before {
        background-color: black;
        color: white;
        display: block;
        font-size: 12px;
      }
      #contents:before {
        content: '(私はcontentsの擬似要素)';
      }
      #none:before {
        content: '(私はnoneの擬似要素)';
      }
      .child {
        font-style: italic;
        margin-left: 1em;
      }

    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div(
          div(
            { id: 'contents' },
            'contentsはコンテナだけが消え、テキストも子どもも擬似要素もみんな残る',
            div({class:'child'},'私はcontentsの子ども')
          ),
          div(
            { id: 'none' },
            'コンテナが消えるときはみんな道連れ',
            div({class:'child'},'私はnoneの子ども')
          )
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = '',
        values = [],
        defaultValue = '',
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: 'radio',
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: 'contents',
          targetPropName: 'display',
          groupName: '.contentsのdisplayプロパティ',
          defaultValue: 'block',
          values: ['block', 'inline', 'contents'],
        }),
        PropsToggler({
          targetId: 'none',
          targetPropName: 'display',
          groupName: '.noneのdisplayプロパティ',
          defaultValue: 'block',
          values: ['block', 'inline', 'none'],
        }),
        SampleBox
      );
    </script>
  </body>
</html>

display: contents;は、コンテナだけをないものとして扱う。一見使いどころに迷うが、レスポンシブデザインのスタイリングに重宝しそうである。

レガシーなやつ

html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
    </script>
  </body>
</html>
displayプロパティの値 概要
inline-block
inline-table
inline-flex
inline-grid

displayの属性値の正確な定義

CSS3では、displayの属性値は以下の様に定義されている。

bnf
<display-outside>  = block | inline | run-in
<display-inside>   = flow | flow-root | table | flex | grid | ruby
<display-listitem> = <display-outside>? && [ flow | flow-root ]? && list-item
<display-internal> = table-row-group | table-header-group |
                     table-footer-group | table-row | table-cell |
                     table-column-group | table-column | table-caption |
                     ruby-base | ruby-text | ruby-base-container |
                     ruby-text-container
<display-box>      = contents | none
<display-legacy>   = inline-block | inline-table | inline-flex | inline-grid

レイアウト別にまとめた方がわかりよかろうと思い、本記事ではこのカテゴライズは踏襲しなかったが、参考までに載っけておく。

どこか間違っていたら、優しく教えていただきたい。

参考資料