世界を動かす技術を、日本語で。

ボタンを使うだけで

概要

  • <div><button> の使い分けに関する誤解の指摘
  • アクセシビリティの観点から <button> が優れている理由の解説
  • role属性tabindex による“修正”の問題点
  • キーボード操作やスクリーンリーダー対応の重要性
  • 最終的に button要素 を使うべき理由のまとめ

フレームワーク開発者が陥りがちな「divとbuttonは同じ」論争

  • ReactやHTMXユーザーの間で <div> をボタン代わりに使う事例の多発
    • 例: <div onclick="showSignIn()"> Open Modal </div>
  • この実装では スクリーンリーダー 利用者に対して操作可能要素であることが伝わらない
  • キーボード操作 (Tab移動やEnter/Space押下)に非対応
  • 著名なReact界隈の人物が「divがbuttonよりアクセシブル」と主張した事例も存在

「修正案」が抱える根本的な問題

  • HTMLの button要素 は暗黙的に[role="button"]を持ち、支援技術に意図が伝わる
  • role属性 をdivに付与しても、フォーカス可能やキーボード操作には非対応
    • 例: <div role="button" onclick="showSignIn()"> Open Modal </div>
  • tabindex="0" でフォーカス可能にできるが、フォーカス順序の混乱や予期せぬ動作を招くリスク
    • 例: <div onclick="showSignIn()" tabindex="0"> Open Modal </div>
  • キーボード操作対応には keydownイベント の追加実装が必要
    • Enter/Space押下時だけ処理するよう分岐
    • フォーカス対象の特定やイベントの伝播制御が煩雑

「全部自作」するくらいなら最初からbuttonを使うべき理由

  • 上記の「修正」を積み重ねると、結局buttonの標準機能を再実装しているだけ
  • button要素なら…
    • role="button" が自動で付与される
    • Tabキー で自然にフォーカス移動
    • Enter/Space で自動的にクリックイベント発火
  • 余計なコードを書く必要がなく、保守性・アクセシビリティ・ユーザビリティ全てで優位

結論:正しい要素を選ぶことが最良の“省力化”

  • Reactや他のフレームワークが好きな開発者ほど、 「書かなくて済むコード」 を重視する傾向
  • button要素 を正しく使うことで、無駄な実装やバグの温床を回避
  • 本質的なアクセシビリティ開発効率 の両立が実現

Hackerたちの意見

変だな、ボタンが使えるときはいつもボタン使うんだけど、必要なものが実際にはボタンじゃなくて、ボタンみたいに動く何か、例えばウェブアプリ内を移動するハイパーリンクみたいなものだったら別だけど。

URLが更新されないなら、それはボタンだと思う。URLが変わるなら、リンクにすべきだね。少なくとも、俺はいつもそうしてる。

ウェブアプリをナビゲートするハイパーリンクみたいなもの? それって <a> タグのこと?

100% 元の意図にできるだけ近い要素を使うべきだよね。

そもそも、なんで誰かがそのオプションを提案するの?

世界を見てると、理由の一つはボタンに変な見た目を適用しやすくするからだと思う。だから、ボタンとして機能しないだけじゃなくて、見た目もボタンじゃなくなっちゃうんだよね。

ところで、TV Tropesって知ってる? あそこには長いリストがあって、1ページ内で「フォルダ」に分かれてるんだ。 その「フォルダ」をクリックすると展開したり折りたたんだりできるんだけど、これは「onclick」プロパティを使った実装で、内部には(昔はそうだったと思うけど、今は兄弟の子供みたいな感じだね)。

一番よく見る理由は、デフォルトのボタンスタイルをオーバーライドしなきゃいけないって愚痴だね。

この記事に追加しておきたいのは、ほとんどのボタンにはtype="button"を設定すべきってこと。デフォルトではボタンはtype="submit"になってて、フォーム内にあるとそのフォームを送信しちゃうんだ。送信動作を無効にする正しい方法を知らない開発者がdivを使っちゃうこともあるだろうけど。

そうだね。苦労して学んだよ、笑

彼らは送信動作を無効にする正しい方法を知らない。これはDIVの利点で、知らないからtype="submit"を誤って設定することがない。多くの人が知らないことはたくさんあるよね。DIVを使うと、空の状態から始めて、追加したい機能を明示的に加えることができるから、後でその機能を簡単に削除したり変更したりできるんだ。もちろん、HTMLの標準についての知識が豊富で、Buttonが現在のユースケースにぴったり合うなら使うだろうけど、デザインや要件は常に変わるからね。DIVを使うことでデザインがより透明になるし、属性を追加することで設定した機能が見えるようになる。Buttonを使うと、あなたやコードを読む他の人が、何をするのかを理解するためにドキュメントを参照したくなるかもしれないけど、DIV + 属性ならそれが明示的に「書かれている」んだ。いくつかの利点と欠点だね。

デフォルトは、のためのものであって、ではないと信じてる。

確かに、OPの長い愚痴は重要な情報を見逃してるね。デフォルトタイプのボタンは変なことをするから、確かクリック時のJSハンドラーをスキップしちゃうんだよね。

俺のもう一つのイライラポイントは、ナビゲーションを実装するのにonclickハンドラを使ってるウェブサイトだね。ちゃんとしたアンカータグを使えば、正しいリンクの動作がタダで手に入るのに:* 中クリックで新しいタブが開く * アクセシビリティデバイスと統合される * 右クリック+新しいウィンドウで開くオプションが使える * などなど。ナビゲーション的なものであれば、JavaScriptのごちゃごちゃを使わずにリンクを使おうよ。

ここ数年でこういうことをする人が増えてるのを見たよ。フレームワークや無知、無関心が関係してるんだろうけど、昔ながらの方法がほぼいつも最高のUXを提供してくれる。タグの代わりにおしゃれにしようとした人には、軽い不快感と不便を願ってるよ。

昔、.netプログラミングをやってた頃、asp.netはナビゲーションをJavaScriptイベントで処理してて、それが全部壊れちゃったんだよね。ちょうどRuby on Railsが出る前だったから、今はもっと良くなってるかも。

そうだね!クリックできるなら、ボタンかリンクのどっちかにすべきだよね。

ああ、100%同意!最近参加したプロジェクトは、そこそこ複雑なReactのウェブアプリなんだけど、ナビゲーション要素が実際のリンクじゃないんだ。すべてonClickで処理されてて、概念的にはただのリンクなのに。なんでこんなことがウェブのフロントエンド界隈で広まってるのか全然わからない。

なんでブラウザは、クリックハンドラで発生するJSトリガーのナビゲーション(またはhistory.push)イベントをキャッチしないんだろう?そのクリックハンドラのイベントデータから、ユーザーが中ボタンを使ったりcmd/ctrlを押しながらクリックしたことがわかるのに、それを新しいタブでのナビゲーションに書き換えられないのかな。理想的には、呼び出し元が期待していたことと違うことが起こったとは気づかせずに。サイトやアプリの開発者が意図したこととは違うかもしれないけど、ユーザーが実際にトリガーしたいと表現しているのはその意味なんだから、ユーザーが望むことをしてあげればいいのに。ブラウザはユーザーエージェントであって、開発者エージェントじゃないからね。(「それはレイヤー違反っぽい」と言う前に—確かにそうだけど、その特定のレイヤー違反は、ほとんどのブラウザのJSエンジンが「ナビゲーション呼び出しのスタックにクリックイベントが含まれていない場合はポップアップウィンドウや新しいタブのナビゲーションを抑制する」ロジックをサポートするために既に存在してる。コードの見た目はもうお金を払って買ったものだから、できるだけその恩恵を受けるべきだよね!)

そうだね。Reactを始めた多くの開発者は、つまらない基本を学ぶことなく「楽しい」部分に飛び込んじゃったと思う。そして、その開発者たちが後に続く人たちに間違ったパターンや基準を設定しちゃった。私が覚えている限り、divをボタンのように装飾する必要があったのは、アコーディオンのトリガーが巨大なボタンで、渡されたものがその中にレンダリングされるときだけだったけど、トリガーのタイトルの右にアクションが必要だったんだ。でも、そんなのは超稀だよね。ボタンをネストするのは無効なHTMLだから、普通にボタンを渡すわけにはいかないし。確かにCSSで絶対位置を使うこともできるけど、それだとフローから外れちゃって、別の方法でハックすることになる。

あと、JSベースのスクロールはやめてほしい。中ボタンを押してスクロールすることが多いんだけど、そんなサイトが多すぎる。

これ、Office 365のMicrosoftのウェブサイトチェッカーを思い出させる。左クリックでリンクをクリックすると、ページが安全チェッカーでキャッチされる(でも半分は機能しない)けど、その後に進む。でも中クリック?安全なんてないよ。

もう何年も前から、画像ホスティングサイトはJavaScriptがないと画像を表示してくれないところまで来ちゃったよ。ほんと、ただのimgタグを使えばいいのに。

これを「その目的のために作られたHTML要素を使えばいいじゃん」と広げてほしいな。普通のSPA開発者は、HTML要素の意味を四分の一も理解してない気がするし、毎回車輪を再発明してる感じ。

そうそう、「プラットフォームを使えばいい」っていうのがあって、HTML5が出た2014年頃からフロントエンド界隈ではよく聞くフレーズだよね。残念ながら、ウェブ開発のすべての部分で広まってるわけじゃないけど、確実に「正しい」やり方として見られてるよ。

だったら、要素がスタイルを適用できるだけだったらいいのに。例えば、日付ピッカーが最悪なんだよね。使いたいけど、JSベースのやつは避けたい。でも、クライアントが「見た目がダサい」って文句言うんだよ。

2010年にアプリを書いた時のことをぼんやり覚えてるけど、その頃はボタンをすべてのブラウザで一貫してスタイルできなかったんだ。Firefoxでは灰色のボックスだったり、他のOSの標準スタイルが使われたりしてた。どこでも同じように見せたかったら、自分たちでボタンを作らなきゃいけなかった。間違ってるかもしれないけど。

これにやられたことがあるよ:ユーザーエージェントのスタイルシートに「button {align-items: flex-start}」って書いてあった(少なくともChromeではね)。デフォルトの動作は「ストレッチ」なんだ。フレックスボックスのサイズが間違ってる理由を一時間もデバッグしてた。できるだけ正しいHTML要素を使いたいけど、どこにでもsを使うのが小さなサイドプロジェクトをすごく楽にしてくれると思う。非デフォルトの動作を全部覚えなくて済むからね。

「フロントエンドを作るならCSSは知っておいた方がいい」って入れておくべきだったな。いいフォローアップだね。言ってくれてありがとう!

appearance: none はボタンスタイルをリセットするのにかなり効果的だよ。私は通常、ボタンのように振る舞いたいもの(フォーカス、アクセシビリティ、インタラクティビティが必要なもの)用に .unbuttonify クラスを作って使ったり拡張したりしてるけど、見た目はハンバーガーメニューのトグルみたいな感じにしてる。

関連して、ボタンについて話す機会があまりないから言うけど:私の本業の共有ライブラリでは、ボタンかアンカータグを使う決定を抽象化するために、Clickableコンポーネントを作ったんだ。スタイルをリセットして、両方の要素が同じように動くようにしてる(デフォルトでもスタイルを適用したときも)。デザイン側でボタンをデザインとして使うのか、機能として使うのかで混乱があったけど、今はその問題が全くなくなった。スタイルが予測可能な方法でリセットされてるから、そもそもdivを使う理由の一つがなくなるんだよね。

スタイルリセットがどうやって要素を同じように動かすの?リンクはボタンよりもブラウザに組み込まれている機能が圧倒的に多いよ。

これはLLMが「悪い」コードを書く傾向がある良い例だね。こういうパターン(ブラウザでのホイール再発明とか)は実際によく見られるし、LLMはネイティブ機能(ボタンとか)を使うよりもこういうのを選びがちなんだ。Claudeに実装を見直して、こういう風にシンプルにするように言うことが多いよ。もう一つの良い例は、TypeScriptでの奇妙なエラーハンドリングの慣習だね。Claudeは色んなアプローチを使って変なやり方をたくさん考え出すけど、「期待される値かエラーを返す」みたいなシンプルなパターンはほとんど考えないんだよね。

これ、LLMがコードを書くのは得意だけど、ソフトウェアエンジニアリングはあんまり得意じゃないっていういい例だね。

何かをする場所を見つけるためにあちこちクリックする世代がいるよね。人々は、うまく設計されてないことを学ぶことに「誇りを持っている」って感じる問題を抱えてる。10年前、リンクをドラッグすることがテキストを選択するよりも重要だって誰かが決めちゃったから、テキストを選ぶのがほとんど不可能になっちゃった。リンクドラッグにふさわしい評価を与えるために、ブラウザをフォークしなきゃいけないかも。それ、多分DIVの連中のせいだね。

これ、もっと取り上げられてもいいポイントだと思う。リンクをドラッグしたいと思ったことはないけど、毎日のようにリンクの一部をハイライトしたり選択したりするのに苦労してる。

俺は変わり者かもしれないけど、リンクをドラッグするのが好きなんだ。そうすると、リンクをバックグラウンドタブで開けるからね。右クリックメニューもあるけど、サイトがイベントをいじってなければ、PC(Windows/Linuxなど)でAltを押したり、macOSでOptionを押したりすると、リンク内のテキストを選択できるんだ。

iOSで電話番号をコピーするのと同じくらい悪いね、電話をかけるんじゃなくて(Androidではどうかは知らないけど)。iOSが電話番号にかけようとするのが、ほとんどの場合自分がやりたいことじゃないから、すごくストレスなんだよね。

リンクを選択するためのブラウザ拡張があるよ。昔は「Select Like A Boss」って呼ばれてたけど、今は「Drag-Select Link Text」って名前になった。要するに、横にドラッグするとテキストを選択、縦にドラッグすると通常通りにドラッグ&ドロップ、長押しするとリンク全体のテキストを選べるってわけ。

テキストを選択するのはほとんど不可能だよね。選択できないテキストはイライラするけど、最近「TextSniper」っていうmacOSアプリを見つけたんだ。それを使うと、マウスでエリアを選択(スクリーンショットを取るときみたいに)できて、その後OCRでテキストを読み取ってクリップボードに入れてくれる。これでGoogle Analyticsがまた使えるようになるかも。