# タッチ画面のある端末ではタップでしか押せないボタン

Source: https://easeweb.dev/learn/touch-detection
Topics: ボタンとリンク > タッチ、マウス、キーボードをいつでも切り替えられる (WCAG 2.5.6)
The fix: どの端末でもclickを待つようにし、touchendを選んでいたタッチ判定を削除します。タップ、マウスのクリック、フォーカスしたボタンでのEnterキーとSpaceキー、スクリーンリーダー、音声操作はどれもclickを発生させるため、リスナー1つで、どの押し方にも応えられます。
Test with: touch, mouse, keyboard, speech input, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/concurrent-input-mechanisms.html https://www.w3.org/WAI/WCAG22/Techniques/failures/F98 https://w3c.github.io/touch-events/#mouse-events https://developer.chrome.com/blog/300ms-tap-delay-gone-away https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/accessibility/ax_object.cc https://searchfox.org/firefox-main/source/accessible/generic/LocalAccessible.cpp https://searchfox.org/firefox-main/source/modules/libpref/init/StaticPrefList.yaml

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

あるショップのマグカップ一覧には、上に「絞り込み」ボタンがあり、押すと色を選ぶパネルが開きます。以前のスマートフォンのブラウザーは、ダブルタップでの拡大に備えて、タップから約300ミリ秒待ってからクリックを送っていました。タップの反応を速くするため、このショップのスクリプトは、ブラウザーがタッチに対応しているかを調べ、対応していれば `click` の代わりに `touchend` を待ちます。スマートフォンでは、指を離した瞬間に「絞り込み」が開きます。

タッチ画面付きのノートパソコンでも、この判定は真になります。タップで「絞り込み」を開き、トラックパッドで色を選んでから、パネルを閉じようと「絞り込み」をクリックしても、何も起きません。キーボードを使う人がTabキーで「絞り込み」に移ってEnterキーを押しても、何も起きません。ChromeやEdgeでは、音声操作で「絞り込みをクリック」と言っても同じです。開け閉めできるのは、画面に触れた指だけです。

## 確認できる問題

レベルAAAのWCAG 2.5.6「入力メカニズムの共存」は、プラットフォームで使える入力方法をコンテンツが制限しないことを求めています。例外は、制限が不可欠な場合、セキュリティのために必要な場合、利用者の設定を尊重するために必要な場合だけです。タッチ画面付きのノートパソコンでは、タッチ、トラックパッドやマウス、キーボードを同時に使え、利用者はいつでも持ち替えられます。W3Cは、このパターンを失敗例F98として挙げています。Touch EventsのAPI、メディアクエリーの `pointer: coarse`、`navigator.maxTouchPoints` などでタッチ画面を見つけ、タッチ専用のリスナーを選ぶものです。ここではどの例外にも当たりません。絞り込みのパネルにタッチが不可欠な理由はなく、この判定はセキュリティとも、利用者が選んだ設定とも関係がありません。

`'ontouchstart' in window` で分かるのは、ブラウザーがタッチイベントに対応していることだけです。タッチ画面付きのノートパソコンのChromeとEdgeは対応しており、WindowsとLinuxのFirefoxもタッチ画面を見つけると対応します。スマートフォンやタブレットのブラウザーは、ほかに何をつないでいても対応しています。マウスが送るのはマウスのイベントと `click` で、`touchend` は送りません。フォーカスしたボタンでのEnterキーとSpaceキーも同じです。スクリーンリーダーや音声操作がブラウザーのアクセシビリティの仕組みを通してボタンを押すと、ChromeとEdgeではマウスのイベントと `click` が届きます。Firefoxは、タッチイベントが有効なら `touchstart` と `touchend` も一緒に送るため、こうした押下でもパネルが開きます。そのため、ChromeとEdgeでは指以外の押し方がすべて、Firefoxではマウスとキーボードが、誰も待っていないクリックとしてページに届きます。Touch Eventsの仕様は、まさにこの判定を避けるよう書いています。タッチイベントに対応していても、タッチ画面だけが入力方法とは限らず、「タッチか、マウスとキーボードか」で分けたリスナーはサイトをタッチ専用にしてしまう、という内容です。F98は、判定が真になる端末では、達成基準2.1.1「キーボード」も満たさないことが多いと述べています。

この判定が避けようとした待ち時間は、もうありません。Chromeでは2014年から、iOSのSafariでは2016年から、viewportのmetaタグで `width=device-width` を指定したページなら、タップの直後にクリックが送られます。

## 利用者への影響

タッチ画面付きのノートパソコン、キーボードカバーを付けた2-in-1のタブレット、Bluetoothのキーボードやマウスをつないだスマートフォンやタブレットは、どれもタッチ画面とほかの入力方法を併せ持つため、影響はスマートフォンにとどまりません。

## 自動チェックとAIの限界

ボタンには名前、役割、`aria-expanded` の状態があり、マークアップのチェックはすべて合格します。スキャンはタッチ画面のないデスクトップのブラウザーで動くため、判定は偽になり、ページは `click` を待ちます。スクリプトでのクリックもキー操作もすべて成功します。axeにはこれを調べるルールがありません。EaseWebの診断は、各ページをタッチ画面を模擬した状態でもう一度開き、コントロールを比べます。1回目には自身の `click` のリスナーがあり、2回目には `touchend` か `touchstart` のリスナーがあって `click`、`pointerup`、`mouseup` のリスナーがないコントロールと、1回目に表示され2回目に隠れているコントロールを、要確認として記録します。確定としないのは、クリックがページの上位の要素で処理されている場合があるためです。フレームワークがページのルートに付けるリスナーは見えません。このコードは、タップの待ち時間を扱った古い回答や解説に今も出てくるため、それを元に生成されたコードにも入り込むことがあります。

## 修正前：タッチ画面があればtouchend

```html
<button type="button" class="filters-toggle" aria-expanded="false" aria-controls="filters">
  絞り込み
</button>
<form id="filters" class="filters" hidden>
  …
</form>
```

```js
const toggle = document.querySelector('.filters-toggle');
const filters = document.getElementById('filters');
// クリックの待ち時間なしで、タップにすぐ反応する。
const press = 'ontouchstart' in window ? 'touchend' : 'click';

toggle.addEventListener(press, () => {
  const open = toggle.getAttribute('aria-expanded') === 'true';
  toggle.setAttribute('aria-expanded', String(!open));
  filters.hidden = open;
});
```

- タッチ画面付きのノートパソコンやキーボードを付けたタブレットでは `'ontouchstart' in window` が真になり、ボタンは `touchend` だけを待ちます。
- マウスのクリックでは `touchend` が発生しないため、「絞り込み」をクリックしても何も起きません。
- EnterキーとSpaceキーはクリックでボタンを押すため、これらでも何も起きません。ChromeとEdgeでは、スクリーンリーダーと音声操作でも同じです。

## 修正後：どの端末でもclick

```html
<button type="button" class="filters-toggle" aria-expanded="false" aria-controls="filters">
  絞り込み
</button>
<form id="filters" class="filters" hidden>
  …
</form>
```

```js
const toggle = document.querySelector('.filters-toggle');
const filters = document.getElementById('filters');

toggle.addEventListener('click', () => {
  const open = toggle.getAttribute('aria-expanded') === 'true';
  toggle.setAttribute('aria-expanded', String(!open));
  filters.hidden = open;
});
```

1. タッチの判定を削除します。どの押し方を受け付けるかを、ページが決めることはなくなります。
2. ボタン本来の `click` を待ちます。タップでは指を離したときに発生し、マウスのクリック、フォーカスしたボタンでのEnterキーとSpaceキー、スクリーンリーダー、音声操作でも発生します。どれを使ってもパネルが開き、いつでも持ち替えられます。

これで修正前の例は直り、2.5.6を満たします。ページに `<meta name="viewport" content="width=device-width">` がある限り、タップの反応も遅くなりません。

## 実装の判断

タッチイベントは、加えて使う分には問題ありません。写真の列を指でスワイプしたり、スライダーをドラッグしたりするために `touchstart` や `touchmove` を待ってもかまいません。ただし、同じことをするボタンを横に置いて `click` で動かし、どの端末でも表示しておきます。同じ間違いはCSSでも起きます。タッチならスワイプで足りると考えて、`@media (pointer: coarse)` や `(hover: none)` でボタンを隠すと、同じ端末のマウスとキーボードからは使えなくなります。`pointer: coarse` が表すのは主なポインターだけなので、キーボードとトラックパッドを付けたタブレットでも当てはまることがあります。

タップの速さを残そうと `touchend` と `click` の両方を待つと、`touchend` で `preventDefault()` を呼ばない限り、1回のタップで処理が2回実行されます。呼んだとしても、ボタンの上で始まったタッチがスクロールに変わると、押したことになる場合があります。`touchend` は、タッチが始まった要素で発生するからです。`touchstart` で実行すると、指を離す前に動いてしまいます。これは[押した瞬間に実行され、キャンセルできないボタン](/ja/learn/pointer-cancellation)で扱っています。`pointerup` も近道にはなりません。キーボードでは発生しないからです。使うのは `click` か、コンポーネントライブラリーの押下イベントのうち、離したときに発生し、キーボードでも発生するものです。ボタンを結び付けるコードには、`isTouch` や `isMobile` の判定を入れないようにします。

## 修正の確認

1. コードで `ontouchstart`、`maxTouchPoints`、`TouchEvent`、`pointer: coarse`、`hover: none` と、`isTouch` や `isMobile` などのフラグを検索します。どれも、コントロールが待つイベントを選んだり、コントロールを隠したりしていません。
2. Chromeのデバイスツールバーでタッチ対応の端末を選んで再読み込みし、ページにタッチ画面を見つけさせます。「絞り込み」をクリックし（デバイスツールバーはクリックをタップとして送ります）、次にTabキーで移ってEnterキーとSpaceキーを押します。どれでもパネルが開閉します。
3. タッチ画面付きのノートパソコン、またはキーボードとマウスを付けたタブレットで、「絞り込み」をタップで開き、マウスで閉じ、キーボードでもう一度開きます。順番を変えても動きます。
4. 同じ端末のChromeかEdgeで、音声操作を使って「絞り込みをクリック」と言い、スクリーンリーダーを使って「絞り込み」に移り、Enterキーを押します。パネルが開き、ボタンが展開と読み上げられます。Firefoxはこうした押下にタッチのイベントも送るため、Firefoxだけで試すと問題を見落とすことがあります。

OS、ブラウザー、支援技術のバージョン、ビルド、日付と、各手順の実際の結果を記録してください。共通のボタンやメニューのスクリプトを変更したら、もう一度確認します。

## 制限

これは出発点となる実装例で、すべてのアプリケーションでの動作を保証するものではありません。ここで扱うのは、タッチの判定で選んだリスナーです。タッチ端末で矢印ボタンを隠したカルーセルは、スワイプが唯一の方法になります。これは[ボタンで代われないピンチ・ドラッグ・傾きのジェスチャー](/ja/learn/gestures)で扱っています。マウスを見つけるとホバーで開くメニューや、本物のキーボードの代わりにページが押し付ける画面上のキーボードも、同じ達成基準を満たさないことがあり、別の確認が必要です。認められる制限もあります。タッチタイピングの練習ソフトはキーボードでの入力を求めてかまいません。それが教える内容そのものだからです。利用者が実際に使うタッチ画面付きのノートパソコンやタブレットで確認してください。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
