タイトル: リンク・ボタン
SEOタイトル: HTML のリンク(<a>)とボタン(<button>)の使い分けとアクセシブル実装
| この記事の要点 |
|
リンクとボタンの正しい使い分け
HTML には別ページや別位置に「移動」させる <a> と、「何かを実行」させる <button> があります。この 2 つは見た目が似ていても、ユーザ・支援技術・SEO 視点では別物です。
| 判断 | 使う要素 | 例 |
|---|---|---|
| 別 URL(または同一ページ内アンカー)に移動 | <a href> | 「商品詳細」「目次へ戻る」「外部サイト」 |
| フォーム送信 | <button type="submit"> | 「送信」「保存」 |
| モーダル開閉・タブ切替・JS で何かを実行 | <button type="button"> | 「開く」「コピー」「削除」 |
| フォーム内容のリセット | <button type="reset"> | 「リセット」 |
| 新規タブで開きたい | <a target="_blank" rel="noopener"> | 外部リンク |
基本構文
<!-- リンク:別ページへ移動 -->
<a href="/products/123">商品詳細を見る</a>
<!-- 外部リンク(noopener + 視覚ヒント) -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
外部サイト
<span aria-hidden="true">↗</span>
</a>
<!-- ボタン:フォーム送信 -->
<form action="/login" method="post">
<input name="email" type="email" required>
<button type="submit">ログイン</button>
</form>
<!-- ボタン:JS でアクション -->
<button type="button" onclick="openModal()">モーダルを開く</button>
<!-- ボタン内に画像・アイコン -->
<button type="button" aria-label="検索">
<svg width="20" height="20"><!-- 虫眼鏡 --></svg>
</button>
との違い
<!-- 古典: input -->
<input type="submit" value="送信">
<!-- 現代: button -->
<button type="submit">送信</button>
<!-- button の利点: HTML をネスト可能 -->
<button type="submit">
<svg class="icon-send"></svg>
<span>送信</span>
</button>
<input> はテキストしか入れられませんが、<button> はアイコン・改行・複数要素を入れられるので、現代では <button type="submit"> が主流です。
「リンクをボタン風」「ボタンをリンク風」の罠
見た目はクラスで自由に切り替えられますが、意味(セマンティクス)は要素そのもので決まります。
<!-- ✅ OK: リンクをボタン風に装飾(遷移する処理) -->
<a href="/signup" class="btn btn-primary">無料登録する</a>
<!-- ❌ NG: 遷移しない処理をリンクで実装 -->
<a href="javascript:void(0)" onclick="deleteUser()">削除</a>
<a href="#" onclick="openModal()">開く</a>
<!-- → 右クリックで「新規タブで開く」できてしまう
→ スクリーンリーダーは「リンク」と読み上げる
→ 遷移しないので混乱 -->
<!-- ✅ 修正: button を使う(クラスで見た目はリンク風に) -->
<button type="button" class="link-style" onclick="deleteUser()">
削除
</button>
アクセシビリティ(ARIA・キーボード操作)
最良はネイティブ要素を使うことです。<button> は最初から:
- Tab フォーカス可能
- Enter / Space で発火
- スクリーンリーダーが「ボタン」と読み上げ
- 無効化 (
disabled) 対応
どうしても <div> や <span> をボタンにしたい場合(既存デザインシステム等):
<!-- 全部自前で実装する必要がある -->
<div
role="button"
tabindex="0"
onclick="doIt()"
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); doIt(); }"
aria-label="保存"
aria-disabled="false"
>
保存
</div>
これだけ書くより、素直に <button> をスタイルした方が圧倒的に楽です。
主要 CSS フレームワークでの実装
| フレームワーク | 典型コード |
|---|---|
| Tailwind CSS | <button class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"> |
| Bootstrap 5 | <button class="btn btn-primary"> |
| Material UI | <Button variant="contained" color="primary"> |
| shadcn/ui | <Button variant="default"> |
| Chakra UI | <Button colorScheme="blue"> |
セキュリティ:target="_blank" の落とし穴
外部リンクを新規タブで開くとき、リンク先 JS から window.opener 経由で元ページを操作される(タブナビング攻撃)リスクがあります。必ず rel を付けます:
<!-- ❌ NG -->
<a href="https://external.com" target="_blank">外部</a>
<!-- ✅ OK -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">外部</a>
<!-- 注: モダンブラウザは target="_blank" で暗黙的に noopener を有効化
しているが、互換性のため明示推奨 -->
無効化(disabled)の挙動の違い
| 要素 | 無効化方法 | クリック | フォーカス |
|---|---|---|---|
<button disabled> | 属性 | 無効 | 飛ばす |
<a> | disabled 属性は無効 | 常に発火 | 飛ばさない |
<a> 無効化したい場合 | href を外す or aria-disabled="true" + CSS pointer-events:none + tabindex="-1" | 無効 | 飛ばす |
UX のベストプラクティス
- 主要 CTA(Call To Action)は1 ページに 1 つだけ強調色。すべてを目立たせると優先度が伝わらない
- テキストは具体的に。「クリック」ではなく「資料をダウンロード」「無料体験を始める」
- ボタンの最小タップサイズは 44×44 px(WCAG / iOS HIG / Material 共通)
- ローディング中は
disabled+ 「送信中…」表示で二重送信防止 - 破壊的操作(削除等)は確認ダイアログを挟むか、警告色 + アンドゥ可能に
FAQ
Q: type を書かない <button> の挙動は?
A: フォーム内では submit 扱いになります。これが原因でモーダル開くつもりがフォーム送信される事故が多発。必ず type="button" を明示してください。
Q: アイコンだけのボタンに aria-label は必要?
A: 必須です。スクリーンリーダー利用者にはアイコンが見えません。<button aria-label="検索"> で必ず代替テキストを。
Q: <a> と <button> でフォーカスリングが違う
A: ブラウザの既定スタイルが異なります。:focus-visible でデザインシステムに合わせ統一しましょう。