リンクかボタンかそれ以外か

あるアクションを引き起こす「モノ」を「リンク」とするか「ボタン」とするかは、何を基準に判断すればよいのでしょうか。ここでは最近の私の解釈を書きます。

最近の私の解釈

最近の私の解釈は「アクションが、ページの遷移かフォーカスの移動を発生させるものはリンク、そうでなければボタン」というものです。この解釈はWAI-ARIA 1.0のある注記がきっかけになっています。

WAI-ARIA 1.0

WAI-ARIA 1.0 (2011年1月18日勧告候補)にはlinkロールbuttonロールがそれぞれ用意されています。中でもlinkロールには私の解釈の直接のきっかけとなった注記があります。

Note: If pressing the link triggers an action but does not change browser focus or page location, authors are advised to consider using the button role instead of the link role.

参考までに訳すと次のようになるでしょうか。

注:もしリンクの押下が、アクションは引き起こすものの、ブラウザーのフォーカスを変えずページのロケーションを変更しないならば、著者はlinkロールの代わりにbuttonロールの使用を検討されたし。

clickイベントのキャンセル

もう1つ考えたことはリンクのclickイベントです。

a要素でマークアップしたリンクをクリックなどするとページ遷移もしくはページ内遷移が発生します(リソースがダウンロードされる場合を考慮しない場合)。これをHTML5風にいうと、a要素のactivation behaviorが実行された、ということになります。

この時、a要素のclickイベントをキャンセルすると、activation behaviorは実行されません。ここで考えたのは、リンクの通常のactivation behaviorを実行すると機能的に破たんするものは、リンクといえるだろうか、ということです*1

ということで、リンクのactivation behaviorを実行しても(clickイベントをキャンセルしなくても)機能的に破たんしないものをリンク、そうでないものはボタンと解釈しています。

解釈の例

解釈は言葉だけでは意味を持ちませんので、いくつかの例を解釈してみます。

送信ボタン

私の解釈がうまく当てはまらないのが送信ボタンです。送信ボタンはページの遷移やフォーカスの移動を発生させますが、ボタンです。私の解釈も所詮は解釈ですので、私も送信ボタンはボタンだと考えています。後付けの解釈よりもHTMLのセマンティクスを優先すべきだと考えています。

送信ボタンはボタンです。

モーダルダイアログを開くリンク

モーダルダイアログを開くモノはリンク、と解釈します。

一般に、ユーザーがモーダルダイアログを開くと、フォーカスはモーダルダイアログの中に移動します。例えば、ファイルを選択するダイアログはモーダルであることが一般的です。Windowsのファイルを選択するダイアログを開くと、フォーカスはダイアログの中に移動し、ダイアログを閉じるまでフォーカスをダイアログの外に移動することはできません*2

このように、フォーカスが移動するので、モーダルダイアログを開く「モノ」はリンクと解釈します。

他にもフォーカストラップが発生するモノを開くモノは、リンクと解釈します。例えばカスタム日付ピッカーを開くモノもリンクだと解釈します。

コンテンツの一部の表示・非表示を制御するボタン

summary要素のように、コンテンツの一部の表示・非表示だけを制御するモノは、ボタンと解釈します。

details要素とsummary要素を使えば良いわけですが、構造上details要素を置けないなどの理由により、自分で同様のインタフェースを実装することがあります。このような場合に、コンテンツの表示・非表示を切り替えるモノはボタンと解釈します。コンテンツの表示・非表示だけを切り替えても、フォーカスは、通常、移動しないからです。

HTML to Platform Accessibility APIs Implementation Guide(2013年10月1日草案)でも、summary要素はbuttonロールにマッピングすることが検討されています。

アコーディオン

アコーディオンは、コンテンツとヘッダーからなるパネルが複数ならび、ヘッダーを操作することでコンテンツの表示・非表示を切り替えるインタフェースです。コンテンツの開閉を行うヘッダー部分は何になるかというと、タブ(tabロール)だそうです。

WAI-ARIA 1.0 Authoring Practices(2013年3月7日草案)のAccordion Design Patternでは、アコーディオンは(複数のタブを選択できる)タブリストとして実装する、とされています。

WAI-ARIA 1.0 Authoring Practicesでは、よくある1つのパネルを開くと他のパネルが閉じるインタフェースはアコーディオンとは言わないようです。タブを1つだけ選択可能なモノは、普通のタブリストと言うのでしょう。言葉の使い分けを厳密に行うかはさておき、アコーディオンのヘッダー部分はタブとして実装することになるようです。

タブリストのタブ

タブリストの中にあるタブはタブ(tabロール)です。WAI-ARIA 1.0 Authoring Practices(2013年3月7日草案)のTab Panel Design Patternを参照してください。

マークアップの例

これまでの解釈をもとにしたマークアップの例をいくつか挙げます。

  • 基本的にパクりです
  • 見出しにあたる要素はすべてh3要素を使っていますが特に意味はありません
  • WAI-ARIAに関連する属性はJavaScriptで動的に追加する想定です

リンク

<a href="#foo">リンク</a>

ボタン

<button type="button">ボタン</button>

button要素のtype属性を省略しても、関連付けられたform要素(form owner)がなければ送信ボタンとしては機能しない(activation behaviorは結果的に何もしないと同じになる)わけですが、最近はtype属性をつける派に傾いています。

<a href="#" role="button">ボタン?</a>

hrf属性をもつa要素にrole="button"を指定するのはHTML5やHTML 5.1 Nightlyでは認められていません。厳しいですね…。

href属性をもたないa要素にrole="button"とすることはできます。ただ、私にはメリットが感じられません。

<a role="button" tabindex="0">ボタン</a>

送信ボタン

<button>送信</button>
<input type="image" alt="送信" src="foo.png">
<input type="submit" value="送信">

input要素は、type="image"の場合はalt属性、type="submit"の場合はvalue属性を使って名前を与えます。HTML5などにはtype="image"の場合、value属性は省略しなくてはならないとひっそり書いてあり注意が必要です。厳しいですね…。

モーダルダイアログを開くリンク

<a href="#dialog1" aria-controls="dialog1"
   aria-expanded="false">ダイアログを開く</a>
<div role="dialog"
     id="dialog1"
     aria-labelledby="dialog1-heading"
     aria-expanded="false">
    <h3 id="dialog1-heading">ダイアログの名前</h3>
</div>

WAI-ARIA 1.0ではコンテンツ側がダイアログに名前を与えるべきとあるので、aria-labelledby属性を使っています。名前を与えるのにaria-labelledby属性を使うかどうかはコンテンツ次第だと思います。

aria-expanded属性は、aria-controls属性が設定されている場合はaria-controls属性が指している要素の開閉情報を、設定されていない場合はその要素自身の開閉状態を表現します。

コンテンツの一部の表示・非表示を制御するボタン

<div>
    <h3><button type="button"
                aria-controls="panel"
                aria-expanded="false">見出し1<span>開く</span></button></h3>
    <div id="panel" aria-expanded="false">コンテンツコンテンツ</div>
</div>

見出しの中にbutton要素があるのは気持ち悪いかもしれません。ただ、ボタンの名前を明確にしようとすると、見出しテキストと操作(開く、閉じる)をbutton要素の中に入れる形になると思います。見出しの外にbutton要素をもってきた場合にはaria-labelledby属性などを使ってボタンの名前を明確にする方が私の好みです。これも、ボタンの名前が冗長すぎるかもしれないとも思っています。

<div>
    <h3 id="panel1-heading">見出し1</h3>
    <button type="button"
            id="panel1-button"
            aria-labelledby="panel1-heading panel1-button"
            aria-controls="panel1"
            aria-expanded="false">開く</button>
    <div id="panel1" aria-expanded="false">コンテンツコンテンツ</div>
</div>

アコーディオン

WAI-ARIA 1.0 Authoring Practices(2013年3月7日草案)のAccordion Design Patternを割と素直に実装した例です。OpenAjax Allianceの例とjQuery UIを参考にしています。

具体的な例は次の通りです。

<div role="tablist" aria-multiselectable="true">
    <h3 role="tab" tabindex="0"
        id="panel1-heading"
        aria-controls="panel1"
        aria-selected="true"
        aria-expanded="true">見出し1<span>閉じる</span></h3>
    <div id="panel1"
         aria-labelledby="panel1-heading"
         aria-hidden="false"
         aria-expanded="true">コンテンツコンテンツ</div>
    <h3 role="tab" tabindex="0"
        id="panel2-heading"
        aria-controls="panel2"
        aria-selected="false"
        aria-expanded="false">見出し2<span>開く</span></h3>
    <div id="panel2"
         aria-labelledby="panel2-heading"
         aria-hidden="true"
         aria-expanded="false">コンテンツコンテンツ</div>
</div>

tablistロールはtabロールを所有するため、アコーディオンのコンテナ(tablistロール)の直下にtabロールが来るようにしています。tabpanelロールもtablistロールの直下に来ていますが、WAI-ARIA 1.0 Authoring Practices(2013年3月7日草案)のAccordion Design PatternにはContained within the tablist is a set of tab/tabpanel pairs.とあるので、きっと大丈夫なのでしょう。

見出し要素にtabロールを設定している理由は次の通りです。

タブ

WAI-ARIA 1.0 Authoring Practices(2013年3月7日草案)のTabpanel Design Patternを割と素直に実装した例です。OpenAjax Allianceの例とjQuery UIに加えて、A Rock n Roll Guide to HTML5 & ARIA (2013)を参考にしました。

すでにイディオムが確立していると言えますが、例を挙げておきます。

<ul role="tablist">
    <li role="presentation">
        <a role="tab" tabindex="0"
           id="panel1-tab"
           href="#panel1" aria-controls="panel1"
           aria-selected="true">タブ1</a></li>
    <li role="presentation">
        <a role="tab" tabindex="-1"
           id="panel2-tab"
           href="#panel2" aria-controls="panel2"
           aria-selected="false">タブ2</a></li>
</ul>
<div role="tabpanel"
     id="panel1"
     aria-labelledby="panel1-tab"
     aria-hidden="false">コンテンツコンテンツ</div>
<div role="tabpanel"
     id="panel2"
     aria-labelledby="panel2-tab"
     aria-hidden="true">コンテンツコンテンツ</div>

タブリストにul/li/a要素を使うイディオムは頻出します。

  • 素の状態(JavaScriptが無効な場合)を考えると、タブリストがTable of Contentsになっている
  • tablistロールの直下にはtabロールが来てほしいが、JavaScriptが無効な場合との一貫性を考えるとa要素が操作対象(tabロール)となるのが自然
    • li要素はrole="presentation"をつけてスキップ
    • a要素にはtabロールを設定できる

2つ目のa要素にtabindex="-1"を指定していますが、フォーカスの処理はJavaScriptで行う想定です。

もともとのリンクかボタンかの話に戻ると、タブはリンクとしてマークアップされていますが、JavaScriptが有効な場合にタブをクリックしてもフォーカスは変化しません*3。あれれ…。role="tab"でタブはタブになりましたし、JavaScriptが無効な場合には遷移が発生するということで、見逃してください。

また、タブパネル群にul/li要素を使わなかった(例ではdiv要素を使っています)理由には次のものがあります。

  • li要素をtabpanelロールにした場合、ul要素(暗黙的にlistロール)の直下にlistitemロールでもgroupロールでもない要素が来てしまう
  • HTML5ではli要素にtabpanelロールは設定できない
  • タブリストとタブパネル群にul要素とli要素を使った場合、両者の対応関係が明示的になるという説(?)もあるが私は賛成できない

    • ul要素は順序のないリストであり、2つのリストのn番目の項目同士が対応していることは明らかではない
    • 表示されていないタブパネルにはaria-hiddenを設定するので、支援技術から見えるタブパネルは1個であり、常に全て見えているタブの個数とは対応しない
  • HTML5(2013年8月6日勧告候補)のImplicit ARIA Semantics

おわりに

話が発散してしまいましたが、最近、私は「アクションが、ページの遷移かフォーカスの移動を発生させるものはリンク、そうでなければボタン」という解釈をしています。そして、その解釈を厳密に適用するのは難しい、という話でした。

*1:実際にはリンクを実行したときにスクロールが起きないように、といった理由でclickイベントをキャンセルすることも多いわけですが…

*2:ファイル選択コントロール(input[type=file])もモーダルダイアログを開きますが、強いていえばボタンだと思います。

*3:タブを切り替えた直後にタブパネルにフォーカスを移動させると、タブを順番に切り替えようとしているユーザーが混乱すると思います。特にキーボードでタブを切り替えようとしているユーザーは現在のタブから2つ以上離れたタブに切り替えることが難しくなると思います。