開発記録
ボタンに見えても、<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