CSSが効かない!?fieldset要素でも簡単にテーブルレイアウトを実現できるdisplay: contents;

UI開発者 宇賀

みなさんこんにちは!UI開発者の宇賀です。
いつもご愛読ありがとうございます!

お申し込みやお問い合わせ画面などで、入力フォームはコーポレートサイトだけでなくBtoCのコンテンツでも度々見かけますよね。

フォーム関連要素であるfieldset要素はコンテンツカテゴリ名の通り、フォームを実装するときに度々登場する要素の1つですが、スタイリング時にクセのある要素としても有名です。

今回は、そんなfieldset要素のスタイリングに最近話題のdisplay: contents;を利用してみようというお話です。

fieldset要素の具体的な利用方法については、以前公開された記事をご参照いただければと思います。

入力フォームでは、1つのキャプションとフォームコントロールのグループを横並びに配置するために、度々table要素が使われているように感じます。レイアウトを目的としてtable要素を利用すると、アクセシビリティ周りで発生するいくつかの問題を解決していかないといけません。

「キャプションとフォームコントロールのグループを表す」という点では、fieldset要素の場合は元々妥当な要素ですから、基本的なアクセシビリティに関する問題は起こりにくくなります。

しかしながら、この要素には前述の通りスタイリング周りの強いクセがあることが悩みどころでした。

fieldset要素では display: table; が効かない

次の例は、fieldset要素とlegend要素を利用してテーブルのような見た目にすることを前提に要素の特徴を考慮せず階層だけに注目してマークアップしたものです。

<fieldset>
    <legend>連絡先</legend>

    <div>
        <label for="dummy-01">電話番号</label>
        <input type="tel" id="dummy-01"></label>

        <label for="dummy-02">メールアドレス</label>
        <input type="email" id="dummy-02"></label>
    </div>
</fieldset>

このときに、次のようなスタイルが当たればテーブルのような見た目になるはずですが、残念ながらうまく反映されません。

// Sass記法
fieldset {
    display: table;

    > legend,
    > div {
        display: table-cell;
    }
}

代わりにflexboxを利用してみても、fieldset要素をコンテナにしている限り狙い通りにはならないでしょう※1

どうしてもこのマークアップでテーブルのようなカラムレイアウトを実現したい場合には、floatプロパティを利用する方法もありますが、widthプロパティを当てなければならないですし、罫線を表現したい場合などでも非常に厄介です。

display: contents;を使ってみる

displayプロパティの値、「contents」は、まるでツリー構造からその要素だけがすっぽり抜けたかのような振る舞いをします。

flexboxやgrid layoutは、コンテナの直下の要素がflexアイテム、gridアイテムとなり、レイアウトすることができますが、コンテナとしたい要素とアイテムとしたい要素の間に他の要素が存在しても、それらにdisplay: contents; を指定するだけで間の要素を飛び越えて階層が離れていてもコンテナとアイテムの関係性を持たせることができます。

つまり、外側に1つ要素を増やし、クセのあるfieldset要素をdisplay: contents;を利用して抜かしてしまうだけで、テーブルのようなカラムレイアウトが実現できるのです。

<div class="form">
    <div>
        <fieldset>
            <legend><span>連絡先</span></legend>

            <div>
                <label for="dummy-01">電話番号</label>
                <input type="tel" id="dummy-01"></label>

                <label for="dummy-02">メールアドレス</label>
                <input type="email" id="dummy-02"></label>
            </div>
        </fieldset>
    </div>
</div>
// Sass記法
.form {
  display: table;
  width: 100%;
  border-collapse: collapse;

  > div {
    display: table-row;

    > fieldset,
    > fieldset > legend {
      display: contents;
    }

    > fieldset {
      > legend > span,
      > div {
        border: 1px solid tomato;
        padding: 10px;
        display: table-cell;
      }
    }
  }
}
fieldset要素とlegend要素でマークアップされている構造でdisplay: contents;を利用してテーブルのビジュアルフォーマットモデルを成立させている例
お名前
連絡先

対応しているブラウザではテーブルのようなレイアウトになっているかと思いますが、みなさんのブラウザではいかがでしょうか?。

今回はdisplay: table;を例にしましたが、flexやgridでもcontentsの恩恵を受けることができるパターンは少なくないでしょう。

まとめ

ビジュアルデザインとセマンティクスの兼ね合いをうまく乗り切るためにfieldset要素をdiv[role="group"]に置き換えるなどWAI-ARIAに頼る方法もありますが、元々ある妥当な要素とCSSだけで解決できるのであれば、そのほうがよりよいですよね。

ただ、display: contents;には注意点があります。サポートしているブラウザがまだ十分とは言えないというのもそうなのですが、本来この値はセマンティクスに影響を及ぼさないとされている※2ところ、実はdisplay: contents;な要素がまるでdisplay: none;のようにアクセシビリティツリーから無視されてしまうというバグがモダンブラウザで確認されているのです。

FirefoxについてはMozillaのフォーラムではバグとして報告されているようです(Setting grid item to display:contents resets its accessible role)。せっかくfieldset要素を採用しても、groupロールが無視されてしまうのでは元も子もないですね...。

サポートされているブラウザもまだまだ難ありですが、バグの件も含めると現段階では使いどころが難しいプロパティです。とはいえ、レスポンシブWebデザインが当たり前の昨今ではflexboxやgrid layoutなど、コンテナとアイテムの親子構造がこれまで以上にスタイリングのポイントになってきていますから、自身の階層を無視させるdisplay: contents;が今後表現の幅を広げる上で活躍するのは間違いないと思います。バグについても解決されることに期待しつつ引き続き注目したいです。

[1] 確認したブラウザの中でIE11ですとfieldsetが正常にflexコンテナになり、子のlegend要素、div要素がflexアイテムとしてレンダリングされました。display: contents;は逆に認識されないので、状況に応じてcontentsとflexを一緒に書いておくとよいかもしれません。
[2] display: contents;に関する詳しい仕様はcontents - CSS Display Module Level 3を参照してください。