はじめに
HTMLの教材やWebサイトを開発者ツールでのぞいていると、見かける fieldset/legend
、これらは一体どういった時に活用するといいのでしょうか?
何が便利なのでしょう?
実装パターンについて学んだことをまとめます。
視覚的情報とマークアップを一致させる
見た目で分かる情報 = コードでも分かる情報
以下のラジオボタンを見てみましょう。
<!-- ❌ 視覚的にはグループ分けされてるが... -->
<p>希望する連絡方法</p>
<div>
<input type="radio" id="email" name="contact" value="email">
<label for="email">メール</label>
</div>
<div>
<input type="radio" id="phone" name="contact" value="phone">
<label for="phone">電話</label>
</div>

ラジオにフォーカスが移動した時、ボイスオーバーでは以下のように見上げられます。
"メール、メールアドレス、ラジオボタン、1/2"
見た目には問題なくても、スクリーンリーダーユーザーには「何の選択肢なのか」が伝わりにくいです
つまり、見た目と実装が一致していない状態が、情報格差 を生み出してしまいます。視覚的ユーザーは「希望する連絡方法」というコンテキストを理解できますが、スクリーンリーダーユーザーは「メール、ラジオボタン」「電話、ラジオボタン」としか認識できず、何に対する選択肢なのかが分かりにくいのです。
ここで、fieldsetとlegendを使って書くと...
<fieldset>
<legend>希望する連絡方法</legend>
<div>
<input type="radio" id="email" name="contact" value="email">
<label for="email">メール</label>
</div>
<div>
<input type="radio" id="phone" name="contact" value="phone">
<label for="phone">電話</label>
</div>
</fieldset>
ボイスオーバーの読み上げ:
"メール、メールアドレス、ラジオボタン、1/2、希望する連絡方法、グループ"
「希望する連絡方法、グループ」という情報が追加されました。これにより、スクリーンリーダーユーザーも「何についての選択肢なのか」を先程よりは理解しやすくなります。
また、こちらの方が明確な構造でどの要素がグループになっているか一目で分かるため、エンジニアにとっても分かりやすいコードになると思います。
特に設定項目が多く、複雑なformなら採用してもいいかもしれません。
fieldsetとlegendの基本
fieldset要素の役割
<fieldset>
<p>私はfieldsetでラップされています</p>
</fieldset>

- デフォルトで枠線が表示される
- 支援技術にグループを伝える
- デフォルトで
role="group"
legend要素の役割
<fieldset>
<legend>グループのラベル</legend>
<!-- グループ化されたコンテンツ -->
</fieldset>

- fieldsetの内容を説明するラベルとして扱われる
- 支援技術がグループを認識する際の名前
- fieldsetの最初の子要素である必要がある
実装パターン
ラジオボタンのグループ化
チェックボックスでも利用されます。
<fieldset>
<legend>希望する連絡方法を選択してください</legend>
<div>
<input type="radio" id="contact-email" name="contact" value="email">
<label for="contact-email">メール</label>
</div>
<div>
<input type="radio" id="contact-phone" name="contact" value="phone">
<label for="contact-phone">電話</label>
</div>
<div>
<input type="radio" id="contact-fax" name="contact" value="fax">
<label for="contact-fax">ファックス</label>
</div>
</fieldset>
フォームのセクション分け
同じ種類の入力項目をセクションごとにグループ化。
フォームで入力しないといけない項目が多く複雑である時に考えると良いと思います。
<form>
<!-- 配送先住所 -->
<fieldset>
<legend>配送先住所</legend>
<div>
<label for="delivery-name">お名前</label>
<input type="text" id="delivery-name" name="deliveryName">
</div>
<div>
<label for="delivery-zip">郵便番号</label>
<input type="text" id="delivery-zip" name="deliveryZip">
</div>
<div>
<label for="delivery-address">住所</label>
<input type="text" id="delivery-address" name="deliveryAddress">
</div>
</fieldset>
<!-- 請求先住所 -->
<fieldset>
<legend>請求先住所</legend>
<div>
<label for="billing-name">お名前</label>
<input type="text" id="billing-name" name="billingName">
</div>
<div>
<label for="billing-zip">郵便番号</label>
<input type="text" id="billing-zip" name="billingZip">
</div>
<div>
<label for="billing-address">住所</label>
<input type="text" id="billing-address" name="billingAddress">
</div>
</fieldset>
</form>
見出しを含むlegend
legendの中には見出し要素(h1~h6)も使用可能です。
※ legendの中にはPhrasing要素のみだが、見出し要素は入れることができる
よってセクションの見出しであり、フォームグループの説明である場合も簡単に表現ができます。
<fieldset>
<legend>
<h2>お客様情報</h2>
</legend>
<div>
<label for="customer-name">お名前</label>
<input type="text" id="customer-name" name="customerName">
</div>
<div>
<label for="customer-email">メールアドレス</label>
<input type="email" id="customer-email" name="customerEmail">
</div>
</fieldset>
disabled属性の活用
fieldsetにdisabled属性を付与することで、fieldset配下全体を無効化できます
※ 無効になるのはfieldsetの子孫で、legend要素の子孫ではないフォーム要素。ただ、legendの中にfieldsetを入れるのはセマンティックではないので、ユースケースとして気にしなくてもいいと思います。
<fieldset disabled>
<legend>プレミアム機能(要ログイン)</legend>
<div>
<input type="checkbox" id="premium-feature1" name="premium">
<label for="premium-feature1">高度な分析機能</label>
</div>
<div>
<input type="checkbox" id="premium-feature2" name="premium">
<label for="premium-feature2">優先サポート</label>
</div>
</fieldset>

注意
legendが最初の子要素ではない
<!-- ❌ legendをdivで囲んでる -->
<fieldset>
<div>
<legend>ラベル</legend>
</div>
</fieldset>
<!-- ✅ legendが最初の子要素 -->
<fieldset>
<legend>ラベル</legend>
<div>
</div>
</fieldset>
legendのないfieldset
<!-- ❌ legendがないとグループとして認識されない -->
<fieldset>
<div>
<input type="radio" name="choice" value="a">
<label>選択肢A</label>
</div>
</fieldset>
ARIAを使った代替手段
fieldsetとlegendを使わない時の代用
※ 「Ariaを使うベストプラクティスはAriaを使わないこと」と言われることもあるくらい、Ariaを正しく扱うことは難しいのでネイティブHTMLを使った実装の方が望ましいです。
aria-labelledby
aria-labelledby と fieldset を紐づけることでボイスオーバーがグループであることを読み上げるようになります。
"お名前、メニューポップアップ、テキストを編集、連絡先情報、グループ"
<h2 id="contact-details">連絡先情報</h2>
<fieldset aria-labelledby="contact-details">
<div>
<label for="name">お名前</label>
<input type="text" id="name" name="name">
</div>
<div>
<label for="email">メールアドレス</label>
<input type="email" id="email" name="email">
</div>
</fieldset>
まとめ
fieldset/legendが必要なケース
- アクセシブルな設計が前提にある
- ラジオボタン、チェックボックス群など複数の関連する選択肢
- 住所情報、支払い情報などセクション分けをした方がわかりやすい時
- 「何についての入力なのか」が個別の
label
だけではわかりにくい時
fieldset/legendが不要なケース
- ログイン、検索、メール登録などシンプルな単一の目的
- 独立した入力項目である
- 追加のコンテキストが不要で、
label
だけで十分理解できる