本文へ

開発記録

ボタンに見えても、<button> にするとは限りません

このサイトの製品へのリンク、記事の目次、ドロワーの操作を例に、見た目を共通化しながら、リンク・操作ボタン・フォーム送信それぞれの HTML の意味とブラウザーの動作を保つ方法を説明します。

林予晴
公開
  • HTML
  • UI のセマンティクス
  • Astro
  • コンポーネント設計

「製品を見る」と「目次を開く」は、角丸や枠線、hover 時の効果を同じにできます。ただし、クリックしたときの役割は違います。前者は別のコンテンツへ移動し、後者は今いるページの UI の状態を変えます。

見た目がボタンだからと両方を <button> で出力すると、ページへの移動までクリック時のスクリプトに任せがちです。逆に、すべての操作を <a href="#"> で書くと、移動先のない操作にリンクを代用することになります。

このサイトでは共通の Button コンポーネントを使っていますが、私はまず操作の目的を確認してから、出力する HTML を選びます。コンポーネント名が表しているのは見た目です。すべての使用箇所の意味まで、その名前で決めるべきではありません。

コンテンツへ移動するなら、実際の移動先を用意します

HTML の <a> に href を指定すると、ページやファイル、同じページ内の位置などへのハイパーリンクを作れます。[1] このサイトのホームにある製品へのリンク、記事ページの「ノート一覧に戻る」、製品ページの「日序のデモを見る」は、いずれもこの用途です。

目立つボタンの見た目にしても、出力する要素は <a> のままです。こうした使用箇所では、共通コンポーネントに as="a" と href を渡します。移動先をクリックイベントの中だけに隠さず、HTML に残すためです。

読者は、ブラウザーがもともと備えているリンク操作でコンテンツを開けます。この選択は、コンポーネントの導入記録でも説明しています。この記事では導入手順を繰り返すのではなく、個々の操作部品を置くときにどう判断するかを扱います。

以下は用途を示す例で、ページ全体のコードではありません。パスは中国語版のルートを使った例として、そのまま残しています。

<a href="/products/daily-order">製品を見る</a> <a href="/notes">ノート一覧に戻る</a>

同じページ内の移動もナビゲーションです。日序の紹介ページにある「日序の仕組みを知る」は #journey にリンクし、ページの後半にある説明へ読者を案内します。別のページへ移らないという理由で、操作ボタンに変える必要はありません。

現在の状態を変えるものが操作ボタンです

記事の目次ドロワーを開く操作には、別のコンテンツ URL がありません。今いるページの UI を開くのが役割なので、このサイトでは <button type="button"> を使います。

プロフィールページのアバターを拡大する操作も同じです。画像の URL へ移動するのではなく、同じページ内で UI の状態を変えます。

HTML の <button> は、マウスやキーボードなどで操作できるインタラクティブな要素です。[2] ただし、適切な要素を選ぶことは出発点にすぎません。ドロワーと拡大表示のウィンドウには、それぞれフォーカス、閉じ方、状態の同期を扱う責任が残ります。

そのため私は、「すでに button を使っている」ことを、インタラクションが完成した証拠にはしません。コンポーネントが提供できるのは見た目と基本構造です。各使用箇所で、その操作部品が約束する動作を実装する必要があります。

フォーム送信は別の意図として明示します

type="button" と type="submit" は、あってもなくてもよい飾りではありません。通常のフォームに関連付けられた使い方では、適切な type を指定していないボタンがフォームを送信することがあります。UI の操作だけを行うものには、明示的に type="button" を指定します。[2]

このサイトのローカルな Button コンポーネントは、<button> を出力するとき、デフォルトで type="button" を付けます。呼び出し側から type="submit" を明示的に渡すこともできます。これは現在のプロジェクトの実装であり、すべてのコンポーネントライブラリに共通するデフォルトではありません。

たとえば、次のコードはフォームでの用途を示したものです。このサイトに、このフォームや保存用 API を追加したという意味ではありません。

<form>
  <button type="button">入力方法を表示</button>
  <button type="submit">データを送信</button>
</form>

前者は説明の表示だけを担当し、後者は送信する意図を表します。データの検証、リクエストの失敗、重複送信への対応は、実際のフォーム側で別途扱う必要があります。submit ボタンがあるだけで、こうした機能まで備わっているとは言えません。

variant に要素を選ばせません

このサイトの Button には variant="link" と as="a" の両方がありますが、制御するものは異なります。

variant="link" が選ぶのは見た目です。as を指定しなければ、コンポーネントは引き続きデフォルトの <button> を出力します。as="a" を指定したときだけ要素がリンクに変わり、使用箇所から href を渡します。

この分離によって、テキスト型の操作ボタンをリンクに見せかける必要がなくなり、目立つページへの入口もナビゲーションの意味を保てます。ただし、開発者は variant の名前だけで判断しないようにする必要があります。

私は現在のコンポーネントの契約に沿って、次を確認します。

使用する意図 選択 確認する内容
ページや同じページ内のセクションへ移動する as="a" 実際の href と移動先のコンテンツ
UI を開く、または閉じる デフォルトの button または as="button" type="button"、操作と状態
フォームを送信する button に type="submit" を指定 フォームとの関連付けと実際の送信処理

この表は、このサイトでよくある用途を判断するためのもので、あらゆるインタラクションを分類したものではありません。たとえば、ネイティブの details の開閉は summary が担います。クリックできる領域をすべて button か anchor のどちらかに当てはめるべきではありません。

スクリプトでリンクを拡張しても、リンクであることは変えません

このサイトの記事目次は、href="#段落識別字" を持つネイティブのリンクを出力します。そのうえで、ブラウザー側のスクリプトがスクロール効果、現在のセクションを示す表示、モバイルの目次ドロワーを閉じる操作を追加します。上のフラグメントは中国語の識別子を使った例なので、表記をそのまま残しています。

ここでは順序が重要です。まず HTML に移動先があり、その後でスクリプトが動作を拡張します。目次をイベントだけで動くテキストの集まりとして先に作り、後からスクリプトで役割を与えるわけではありません。

ただし、実際のリンクがあるからといって、周辺の操作部品まで JavaScript なしで動くとは限りません。このサイトのモバイル目次には、noscript 内のネイティブなリンク一覧もあります。一方、ドロワーの開閉やスクロール位置の表示は、引き続きスクリプトによる拡張です。検証時には、これらを分けて確認する必要があります。

コンポーネント名だけでなく、出力を確認します

画面を見るだけでは、角丸の操作部品が <a> なのか <button> なのかは分かりにくいものです。私は実際の HTML に戻り、タグ、href、type、操作部品の名前を見て、使用する意図と一致しているかを確認します。

その後に動作を確認します。リンクから本文へ移動できるか、ボタンで実際に目次が開くか、アイコンだけの操作部品に理解できる名前があるか、キーボード操作が用途に合っているか、といった点です。どれも実際の使用箇所で検証する必要があり、別のボタンで成功した結果を代わりにはできません。

共通のスタイルで重複は減らせますが、「コンテンツを見る」ことと「状態を変える」ことは、別の責任のままです。コンポーネントで見た目をそろえながら、その違いは HTML に残したいと考えています。

記録の範囲

この記事は、このサイトの共通 Button のローカル実装、製品ページのリンク、プロフィールページの操作部品、記事目次をもとにまとめています。フォームの節は用途の例であり、このサイトに新しいデータ送信機能があることを示すものではありません。また、すべてのブラウザー、支援技術、操作経路の検証を完了したとも主張していません。コンポーネント導入と分離テストの経緯は、bejamas/ui の導入:まず Button を一つ追加し、コンポーネントライブラリにサイトを任せきりにしない(繁体字中国語)を参照してください。

参考資料

[1] https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a [2] https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button