divはボタンではない、ボタンの実装について知っておくべきすべてのこと
Post on:2023年2月9日
クリックできるボタンを実装するとき、HTMLの何をよく使用しますか?
button
タグ、もしくはdiv
タグ? div
を使用してはいけない理由、button
を使用するときの注意点、場合によってはa
がよい理由を紹介します。
Everything you didn’t know you need to know about buttons
by Steve Sewell
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- ボタンの実装にdivを使用したときの問題点
- ボタンをbuttonで実装する
- buttonのスタイルに関する問題点
- buttonのスタイルを正しく設定する方法
- フォーム内のbuttonの動作を修正する
- 他のページへのリンクに使用する場合はaタグで
- ボタンの実装をコンポーネントにする
- 終わりに
はじめに
クリックできる要素をHTMLで実装するときにa
タグやbutton
タグ、あるいはまったく別のタグ(div
?)のどれを使用しますか?
1 2 3 4 5 6 7 8 9 10 11 12 |
// 🚩 export function MyButton() { return <div onClick={...}>Click me</div> } //❓ export function MyButton() { return <button onClick={...}>Click me</button> } //❓ export function MyButton() { return <a onClick={...}>Click me</a> } |
その答えはあなたが思っていたのと少し異なり、中には驚くようなことがあるかもしれません。
ボタンの実装にdivを使用したときの問題点
まずは、1つ明確にすることから始めましょう。クリック可能な要素にdiv
を使うべきではありません(少なくとも99%のケースで)。
それは、なぜでしょうか?
簡単に説明すると、「divはボタンではない」からです。div
は特別な意味を持たないコンテナで、クリック可能な要素が持つべき次の性質が欠けています。
参考: div要素 -HTML Living Standard
div
は、フォーカス可能な要素ではありません。たとえば、デバイス上の他のボタンのようにtabキーでdiv
にフォーカスが当たることはありません。- スクリーンリーダーなどの支援ツールは、
div
をクリック可能な要素として認識しません。 div
はフォーカスされても、spaceバーやreturnキーなどの特定キーによる入力をクリックに変換しません。
ただ、これらの問題はtabindex="0"
やrole="button"
といった属性で回避することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 🚩 divをほぼボタンのように動作させる試み... export function MyButton() { function onClick() { ... } return ( <div className="my-button" tabindex="0" // divをフォーカス可能にします role="button" // スクリーンリーダーにクリック可能な要素であると設定します onClick={onClick} onKeydown={(event) => { // フォーカス時のspaceバーとreturnキーを呼び出します // クリックハンドラを手動で if (event.key === "Enter" || event.key === "Space") { onClick() } }} > Click me </div> ) } |
そして、ボタンにはフォーカスが当たったというユーザーフィードバックがあるので、ほぼボタンのdiv
にもフォーカス時のスタイルを設定する必要があります。アクセシビリティに関する懸念事項もすべてクリアする必要があります。
1 2 3 |
.my-button:focus-visible { outline: 1px solid blue; } |
ボタンの重要な動作をすべて手動で実装するということは、大変な作業です。しかし、幸運なことに、(ほとんどの場合)より良い方法があります!
ボタンをbuttonで実装する
button
タグの優れている点は、デバイス上の他のボタンと同じように動作し、ユーザーや支援ツールが期待するものと全く同じであることです。
フォーカス可能、アクセス可能、キーボード入力可能、フォーカス状態に準拠したスタイルなど、さまざまな機能を備えています!
1 2 3 4 5 6 7 8 |
// ✅ export function MyButton() { return ( <button onClick={...}> Click me </button> ) } |
しかし、button
には気をつけなければならない問題がいくつかあります。
buttonのスタイルに関する問題点
button
の実装で一番悩ましいのは、button
のスタイルです。
たとえば、ボタンの背景を薄紫色にしたいとします。
1 2 3 4 5 6 7 8 9 |
<button class="my-button"> Click me </button> <style> /* 🤢 */ .my-button { background-color: purple; } </style> |
非常に残念な結果になります。
なんだか、古いWindowsのボタンに見えます。
この原因はbutton
要素にブラウザは奇妙なスタイルを強制するので、独自のスタイルを適用しても混乱するだけです。
おそらく、これがボタンをdiv
で実装する理由でしょう。div
にはブラウザ独自のスタイルはありません。常に期待どおりに機能し、期待どおりに表示されます。
appearance: none;
で外観をリセットすればいいんのでは? と思うかもしれません。しかし、これはあなたが考えているようなことはありません。
1 2 3 4 5 6 7 8 9 |
<button class="my-button"> Click me </button> <style> .my-button { appearance: none; /* 🤔 */ background-color: purple; } </style> |
appearance: none;
を設定しても同じです。
buttonのスタイルを正しく設定する方法
button
をスタイルするには、行ごとにプロパティをリセットする必要があります。
1 2 3 4 5 6 7 8 9 |
/* ✅ */ button { padding: 0; border: none; outline: none; font: inherit; color: inherit; background: none } |
このCSSでdiv
のように見えて動作するbutton
ができます。さらに、ブラウザのデフォルトのフォーカススタイルも使用できるという利点もあります。
もうひとつの方法として、all: unset;
を使用して、特別なスタイル設定をしないようにすることもできます。
1 2 3 |
/* ✅ */ button { all: unset; } button:focus-visible { outline: 1px solid var(--your-color); } |
フォーカス時のスタイルを追加することを忘れないでください。たとえば、十分なコントラストを備えたブランドカラーでアウトラインを設定します。
フォーム内のbuttonの動作を修正する
button
タグを使用する際には、もう一つ注意しなければならないことがあります。form
内にあるbutton
は、デフォルトで送信ボタンとして扱われるため、クリックされるとフォームが送信されます。え、何?
1 2 3 4 5 6 7 8 9 10 |
function MyForm() { return ( <form onSubmit={...}> ... <button type="submit">Submit</button> {/* 🚩 ラベルが「Cancel」でもクリックするとフォームは送信されます。 */} <button onClick={...}>Cancel</button> </form> ) } |
そうなんです、button
のデフォルトのtype
属性がsubmit
だからです。これは奇妙で、イライラします。
これを解決するには、ボタンが実際にフォームを送信するものでない限り、常にtype="button"
を追加する必要があります。
1 2 3 4 5 6 7 8 9 |
export function MyButton() { return ( <button type="button" // ✅ onClick={...}> Click me </button> ) } |
これで、ボタンが最も近いform
の親を見つけて送信しようとすることがなくなりました。ふぅ、面倒ですね。
他のページへのリンクに使用する場合はaタグで
ここで、このルールの大きな例外を一つ紹介します。
他のページへのリンクには、button
を使用したくありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 🚩 function MyLink() { return ( <button type="button" onClick={() => { location.href = "/" }} > Don't do this </button> ) } |
button
にクリックイベントを使用してページにリンクするボタンを実装した場合、問題点がいくつかあります。
button
はクロール可能ではないため、SEO的に非常に不利です。- ユーザーは
button
で実装されたリンクを新しいタブやウインドウで開くことができません。たとえば、右クリックで「新しいタブで開く」など。
そのため、ナビゲーションにbutton
を使用するのはやめましょう。
そこで頼りになるのが、a
タグです。
1 2 3 4 5 6 7 8 |
// ✅ function MyLink() { return ( <a href="/"> Do this for links </button> ) } |
a
タグを使用するもっとも重要な点は、フォーカス可能、アクセス可能、キーボード入力可能であるといった前述のbutton
の利点をすべて備えていることです。そして、ファンキーなスタイルは必要ありません!
ここで「ん?待てよ」と思うかも知れません。a
がbutton
の利点を持ちながら、ファンキーなスタイルの必要がないなら、すべてa
で実装すればいいのでは?
1 2 3 4 5 6 7 8 |
// 🚩 function MyButton() { return ( <a onClick={...}> Do this for links </a> ) } |
しかし、その答えはダメです。href
を持たないa
タグはボタンのように動作しないからです。そうです、a
はhref
に値があるときだけ、フォーカス可能などのボタンの動作が可能になります。
したがって、ボタンにはbutton
を、リンクにはa
を使用するようにしましょう。
ボタンの実装をコンポーネントにする
私が非常に気に入っているパターンの一つは、これらのルールをコンポーネントでカプセル化することです。つまり、MyButton
コンポーネントを用意し、URLを指定すればリンクになり、そうでなければボタンになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
function MyButton(props) { if (props.href) { return <a className="my-button" {...props} /> } return <button type="button" className="my-button" {...props} /> } // <a href="/">をレンダリング <MyButton href="/">Click me</MyButton> // <button type="button">をレンダリング <MyButton onClick={...}>Click me</MyButton> |
こうすることで、ボタンの目的がクリックハンドラであろうと、他のページへのリンクであろうと、デベロッパーとユーザーの両方のエクスペリエンスに一貫性を持たせることができます。
そして締めくくりとして、いくつかのタイプも追加してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type AnchorProps = React.AnchorHTMLAttributes<HTMLElement> type ButtonProps = React.ButtonHTMLAttributes<HTMLElement> type MyButtonProps = AnchorProps | ButtonProps function isAnchor(props: MyButtonProps): props is AnchorProps { return (props as AnchorProps).href !== undefined } export function MyButton(props: MyButtonProps) { if (isAnchor(props)) { return <a className="my-button" {...props} /> } return <button type="button" className="my-button" {...props} /> } |
終わりに
ここまで、たくさんありましたね。
要するに、リンクにはhref
プロパティを持つa
タグを使用し、他のすべてのボタンにはtype="button"
を持つbutton
タグを使用するということです。
sponsors