🦅

<button>をネストするとどうなるか

2024/07/23に公開

validateDOMNesting

Reactで開発をしていると下記のエラーに遭遇することがあります。

Warning: validateDOMNesting(...): <button> cannot appear as a descendant of <button>.

これは、HTML要素のネスト関係が不適切になっていることを警告するエラーです。

たとえば以下のようなbuttonがbuttonにネストされている記述です。

<button>
    Click to select me
    <button className="trash">🗑️</button>
</button>

validateDOMNestingの解消方法

エラーを解消するにはどちらかの要素を他の要素に置き換えればいいのですが、divなどに変更するだけだとその要素が実際にはボタンであるということがスクリーンリーダーに伝わらないというアクセシビリティ的な問題があります。

そのため、以下を実施してdiv要素に変更しつつアクセシビリティを担保するのがおすすめです。

  • WAI-ARIAのroleでボタンであることを伝える
  • tabindexを指定してキーボードフォーカスを受け取れるようにする
<div role="button" tabindex="0">
    Click to select me
    <button className="trash">🗑️</button>
</div>

これでbuttonとしての役割を表現しつつエラーが解決されるはずです。

buttonをネストすると何が起こるのか

純粋なHTMLでの動作

なぜReactが警告を出すのでしょうか。まず、Reactを介さず純粋なhtmlでネストされたボタンを読み込んでみましょう。

<button>
  Click to select me
  <button className="trash">🗑️</button>
</button>

その結果が以下になります。描画されているDOMではボタンがネストされていないことが確認できると思います。

これは、ブラウザがbuttonのネストを無効な構文とみなしてエラー処理、つまりbuttonを並列にする処理を行った結果です。

この処理の説明は、HTML Standardで確認できます。

A start tag whose tag name is "button"
If the stack of open elements has a button element in scope, then run these substeps:

  1. Parse error.
  2. Generate implied end tags.
  3. Pop elements from the stack of open elements until a button element has been popped from the stack.

タグ名が"button"である開始タグの場合:
開いている要素スタックの中にbutton要素が存在する場合、以下を実行する

  1. エラーを発生させる。
  2. 暗黙的な終了タグを生成する。
  3. ボタン要素がスタックから取り除かれるまで、開いている要素のスタックから要素を取り除く。

Reactでの動作

一方でReactはブラウザのHTMLパーサーを利用しないからか、ネストされたbuttonをそのままブラウザで表現できます。そのため、代わりにvalidateDOMNestingエラーを出しているのだと思います。

終わりに

自分は特にHTMLに詳しいわけではなく、validateDOMNestingが出てからはじめて調べた形なのでもし誤っている箇所等あれば指摘していただければ幸いです。

X (Twitter): @koyo_k0

Discussion