# 担当者につながらないサポートのチャットボット

Source: https://easeweb.dev/learn/human-help
Topics: メニューとナビゲーション > ヘルプのチャットボットから、人に問い合わせる方法も分かる (best practice; related to WCAG 3.2.6)
The fix: チャットの中で、最初のメッセージから担当者への連絡方法を示します。「担当者に問い合わせる」ボタンでチームへの短いメッセージフォームを開いて会話も一緒に送るか、最小の変更として、チャットの下部にスタッフのメールアドレスと返信の目安を書きます。どちらの場合も、ボットが理解できなかったときの返事でその方法を案内します。
Test with: keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/consistent-help.html https://www.w3.org/WAI/WCAG2/supplemental/patterns/o7p01-human-help/ https://www.w3.org/TR/coga-usable/#provide-human-help-pattern

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

## 場面

家具のオンラインショップBirchwood Homeは、質問にチャットボット「Birchwoodに質問」で答えています。どのページでも同じ隅にあり、24時間休みません。知っている話題は注文の追跡、返品、配送日数の三つで、問い合わせの大半はこれで足ります。ある客の新しいソファが、脚が1本足りない状態で届きました。チャットを開いて「ソファの脚が1本足りません」と書くと、ボットは「すみません、よく分かりませんでした。下の項目からお選びください。」と返します。「注文の追跡」を押すと、10月22日（木）に配達済みと分かりますが、それは知っています。「返品」はソファを送り返す方法の説明で、望んでいることではありません。「替えの脚がほしい」「人と話せますか」と書き直しても、返事は毎回同じです。ヘルプのページにも同じ三つの話題が並ぶだけで、サイトのどこにもメールアドレス、フォーム、電話番号がありません。

同じ行き止まりは、ほかの形でも起きます。

- ボットが何度か答えられなかった後にだけ、担当者への連絡方法が出てくる。ボットが「理解できなかった」のではなく「誤解した」場合、その方法は出てきません。たとえば、話題の言葉を一つ含むだけの質問には、その話題の答えが返ります。ボットにとっては、それも一つの答えだからです。
- フッターの「お問い合わせ」のリンクが、同じボットを開く。

## 確認できる問題

担当者への連絡方法を求める達成基準はなく、このページはいちばん近い基準も満たしています。達成基準3.2.6「一貫したヘルプ」は、チャットボットを「完全に自動化された連絡手段」としてヘルプに数え、複数のページで繰り返されるヘルプをどのページでも同じ場所に置くことだけを求めています。「Birchwoodに質問」はそうなっています。この基準の解説書（Understanding）は、サイトがヘルプを用意すること自体も、担当者が常に対応できることも求めていないと述べています。そのため、このガイドで扱うのは基準の不適合ではなく、基準の解説自身が勧めるベストプラクティスです。

解説書は、ボットについても具体的に書いています。チャットボットは、誤字を認識し、3回試しても満足な答えを返せなければ人への連絡先を示し、1回の操作で閉じられるなら、多くの人、特に認知障害のある人に役立つというものです。W3Cの認知アクセシビリティのガイダンスには、これを扱うパターン「Provide Human Help」があります。行き詰まったとき、たいていいちばん効果があるのは人の助けであり、複雑な仕組みを使いこなせる人にしか届かない連絡方法では、いちばん必要な人が締め出されると述べています。

ボットは作られたとおりに動いています。メッセージの中に知っている言葉を探し、見つからなければ決まった返事（フォールバック）を返します。その返事が同じ三つの話題を指すため、話題にない質問は同じところを回り続け、パネルのどこにもほかへの道がありません。返事を一つずつ読めば、どれも丁寧で正しい内容です。

## 利用者への影響

ボットが答えられないのは、決まった答えのない質問です。部品が足りない、請求がおかしい、届くはずのものが届かない。こうした質問こそ、いちばん人の助けが必要です。そして、書くことや入力することがもともと難しい人ほど、先にこの堂々巡りに入ってしまいます。

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

axeには、チャットの内容を調べるルールがありません。EaseWebの診断にもありません。スキャンはチャットで会話をしませんし、ウィジェットの多くは外部サービスのフレームの中で動きます。送ったメッセージを誰かが読むかどうかは会社の体制の問題で、ページには表れません。EaseWebの診断は、チャットウィジェットを見つけると、メッセージが読み上げられるかを人が確かめるよう案内します。そのときに、担当者への連絡方法も合わせて確認してください。AIのコードアシスタントにサポート用のボットを頼むと、担当者への連絡方法を求めない限り、ここと同じように話題を並べ直すだけのフォールバックを書くことがあります。

## 修正前：話題を並べ直すだけのチャットボット

```html
<section class="chat" aria-labelledby="chat-title">
  <h2 id="chat-title">Ask Birchwood</h2>
  <div class="chat-messages" role="log" aria-label="Messages">
    <p>Hi! I can help with tracking, returns and delivery times.</p>
  </div>
  <div class="chat-topics">
    <button type="button">Track my order</button>
    <button type="button">Returns</button>
    <button type="button">Delivery times</button>
  </div>
  <form class="chat-form">
    <label for="chat-message" class="visually-hidden">Message</label>
    <input id="chat-message" autocomplete="off">
    <button>Send</button>
  </form>
</section>
```

```js bot.js
const answers = {
  track: 'Order 4821 was delivered on Thursday 22 October.',
  return: 'You can start a return under Your orders within 30 days. We collect large items for free.',
  delivery: 'Most orders arrive within five working days.'
};
const fallback = 'Sorry, I didn’t get that. Try a topic below.';

function reply(message) {
  const text = message.toLowerCase();
  const topic = Object.keys(answers).find((word) => text.includes(word));
  return topic ? answers[topic] : fallback;
}
…
```

チャットそのものは正しく作られています。メッセージは名前の付いたログで、入力欄にはラベルがあり、話題はボタンです。足りないのは、その先へ進む道です。三つの言葉のどれも含まない質問にはフォールバックが返り、フォールバックは同じ話題を指し、パネルのどこにも担当者への道がありません。

## 修正後：チャットの中の「担当者に問い合わせる」ボタン

```html
<section class="chat" aria-labelledby="chat-title">
  <h2 id="chat-title">Ask Birchwood</h2>
  <div class="chat-messages" role="log" aria-label="Messages">
    <p>Hi! I can help with tracking, returns and delivery times.</p>
  </div>
  <div class="chat-topics">
    <button type="button">Track my order</button>
    <button type="button">Returns</button>
    <button type="button">Delivery times</button>
  </div>
  <button type="button" class="chat-person" aria-expanded="false" aria-controls="person-form">Talk to a person</button>
  <form id="person-form" class="person-form" hidden>
    <p>Someone on our team will reply by email within one working day. This chat is sent with your message.</p>
    <label for="person-message">What do you need help with?</label>
    <textarea id="person-message" name="message"></textarea>
    <label for="person-email">Your email</label>
    <input id="person-email" name="email" type="email" autocomplete="email">
    <button>Send to our team</button>
    <p class="person-sent" role="status"></p>
  </form>
  <form class="chat-form">
    <label for="chat-message" class="visually-hidden">Message</label>
    <input id="chat-message" autocomplete="off">
    <button>Send</button>
  </form>
</section>
```

```js bot.js
const answers = {
  track: 'Order 4821 was delivered on Thursday 22 October.',
  return: 'You can start a return under Your orders within 30 days. We collect large items for free.',
  delivery: 'Most orders arrive within five working days.'
};
const fallback = 'Sorry, I didn’t get that. Try a topic below, or talk to a person.';

function reply(message) {
  const text = message.toLowerCase();
  const topic = Object.keys(answers).find((word) => text.includes(word));
  return topic ? answers[topic] : fallback;
}
…
const personButton = document.querySelector('.chat-person');
const personForm = document.querySelector('#person-form');

personButton.addEventListener('click', () => {
  personForm.hidden = false;
  personButton.setAttribute('aria-expanded', 'true');
  personForm.querySelector('textarea').focus();
});

personForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  const body = new FormData(personForm);
  const chat = [...document.querySelectorAll('.chat-messages p')].map((p) => p.textContent);
  body.append('chat', chat.join('\n'));
  await fetch('/support/messages', { method: 'POST', body });
  personForm.querySelector('.person-sent').textContent = 'Sent. We’ll reply by email within one working day.';
});
```

ボタンは最初のメッセージのときから話題の下にあるので、ボットにつまずく前に見つけられます。ボットがあきらめたときには、フォールバックもこのボタンを案内します。ボタンで開くのは2項目だけのフォームで、認知アクセシビリティのガイダンスが勧める短い経路です。誰がいつ返信するかを示し、会話も一緒に送るため、同じ説明を繰り返す必要がありません。送信したことはステータスの行で伝わります。フォームの送信先はスタッフの受信箱です。誰も読まない連絡方法につながるボタンは、同じ人たちを後で困らせるだけです。

## 修正後：最小の変更として、チャットにメールアドレス

```html
<section class="chat" aria-labelledby="chat-title">
  <h2 id="chat-title">Ask Birchwood</h2>
  <div class="chat-messages" role="log" aria-label="Messages">
    <p>Hi! I can help with tracking, returns and delivery times.</p>
  </div>
  <div class="chat-topics">
    <button type="button">Track my order</button>
    <button type="button">Returns</button>
    <button type="button">Delivery times</button>
  </div>
  <form class="chat-form">
    <label for="chat-message" class="visually-hidden">Message</label>
    <input id="chat-message" autocomplete="off">
    <button>Send</button>
  </form>
  <p class="chat-person">
    Rather write to a person? Email <a href="mailto:help@birchwood.example?subject=Help%20with%20order%204821">help@birchwood.example</a>. We reply within one working day.
  </p>
</section>
```

```js bot.js
const answers = {
  track: 'Order 4821 was delivered on Thursday 22 October.',
  return: 'You can start a return under Your orders within 30 days. We collect large items for free.',
  delivery: 'Most orders arrive within five working days.'
};
const fallback = 'Sorry, I didn’t get that. Try a topic below, or email help@birchwood.example to reach a person.';

function reply(message) {
  const text = message.toLowerCase();
  const topic = Object.keys(answers).find((word) => text.includes(word));
  return topic ? answers[topic] : fallback;
}
…
```

メッセージ欄の下に1行足すだけです。この行は最初のメッセージからパネルにあり、フォールバックも同じアドレスを案内します。リンクの文字は「メールする」ではなくアドレスそのものにします。メールアプリを設定していないパソコンでは mailto: リンクを押しても何も開かないことがあり、そのときはアドレスを読んでコピーするしかないからです。件名をあらかじめ入れておけば、チームはどの注文の話かが分かります。ただし、役に立つのは、誰かがその受信箱を読み、書いた時間内に返信している間だけです。

## どの方法を選ぶか

どちらも、最初のメッセージからチャットに担当者への道を示します。「担当者に問い合わせる」ボタンは、利用者をチャットの中にとどめ、会話も一緒に送り、メールアプリも要りません。ただし、送信先のフォームとそれを読むチームが必要です。チャットを変更できる場合や、外部サービスにスタッフへの引き継ぎ機能がある場合に選びます。メールの行は、どのチャットの下部にも、その横のページにも足せる1行です。ウィジェットが外部サービスのもので、設定でできることがそこまでの場合に選びます。

## 実装の判断

ボットが答えられなかった後にだけ担当者を案内する方法は、よくある折衷案で、3.2.6の解説書も「3回」という形で触れています。これは設計の目標ではなく、ボットが最低限すべきことと考えてください。ボットは自分の答えが間違っていたことに気付けません。足りない脚の質問に配送日数を返しても、ボットにとっては答えなので、答えられなかった回数を数えても、その回は数えられません。認知アクセシビリティのガイダンスも、担当者にたどり着くまでに何段階も踏ませることを、避けるべきこととして挙げています。

担当者への連絡方法は、それぞれ次の点に注意します。

- ボットから引き継ぐスタッフとのチャット：引き継いだことを伝え、誰も対応できない時間には、フォームやメールアドレスを代わりに示す。「ただいま担当者が対応できません」だけで終わらせない。
- メッセージのフォーム：2〜3項目にとどめ、返信の目安を書き、最初の修正のように送信したことをステータスメッセージで伝える。手前にCAPTCHAを置かない。
- メールアドレスや電話番号：文字で書き、返信の目安や受付時間を添える。電話番号だけでは足りないことは、[電話でしか連絡できないサポート](/ja/learn/support-contact)で説明しています。

大規模言語モデルを使ったボットは、「分かりませんでした」と言わないことがあります。間違っていても流ちょうに答えられるため、担当者を案内するフォールバックが一度も出てこないことがあります。こうしたボットでも、担当者への道は最初からパネルに置いてください。

ボタンや案内の言葉は、そのまま分かるものにします。「担当者に問い合わせる」「チームにメールする」は分かりやすく、「エスカレーション」「有人対応へ切替」やヘッドセットのアイコンだけでは伝わりにくくなります。入力欄に「担当者」「人と話したい」などと書くと同じ方法を案内するボットもあり、それも役立ちますが、ボタンや案内の行と併せて使います。その言葉を書けばよいと知らない人は、見つけられないからです。

ヘルプへのほかの入り口も、同じ人たちにつながるようにします。同じボットを開く「お問い合わせ」のリンクは、この問題の2つ目の形です。チャットと担当者への連絡方法は、どのページでも同じ場所に置いてください。詳しくは[ページごとに場所が変わるヘルプリンク](/ja/learn/consistent-help)で説明しています。チャットの起動ボタンや閉じるボタン、メッセージの読み上げについては、[キーボードで開けず、閉じるボタンにも名前がないチャット](/ja/learn/chat-launchers)と[メッセージを読み上げないチャットウィジェット](/ja/learn/chat-widgets)で扱っています。

## 修正の確認

1. チャットを開き、何も入力する前に、パネルの中で担当者への連絡方法を見つける。
2. ボットの話題にない質問を、自分の言葉で、誤字を交えて送り、返事が担当者への連絡方法を案内するか確認する。
3. 担当者への連絡方法それぞれでテストのメッセージを送り、チャットに書かれた時間内に、ボットに戻されることなく担当者から返信が来るか確認する。
4. キーボードだけで、またスクリーンリーダーを使って、担当者への連絡方法にたどり着き、メッセージを送り、送信したと伝わるか確認する。
5. サイトのすべての「お問い合わせ」「ヘルプ」「サポート」のリンクをたどり、ボットしか開かないものがないか確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。ボットの話題、ウィジェット、サポート窓口の対応時間を変更した際は、再度確認します。

## 制限

このサンプルは、どのサイトでも同じ結果になることを保証するものではありません。担当者への連絡方法が役立つかどうかは、その先の人たち次第です。返信に1週間かかったり、注文を変更できないチームだったりすれば、同じ客が後で困ります。このガイドが扱うのは担当者にたどり着けるかどうかで、ボット自体の作り方ではありません。最初の質問から返信までの経路全体を、客の中にいるさまざまな人と一緒に確認してください。

## スクリーンリーダーでの動き（EaseWebが実機で確認）

`aria-expanded`: ボタンが開くメニューやセクションが、開いているかどうかを示します。

| スクリーンリーダー | ブラウザーとOS | 押す | Tabで移動 |
| --- | --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 | 未確認 |
| TalkBack | Chrome, Android | 未確認 | 未確認 |

`role="status"`: polite のライブリージョンです。変化は、スクリーンリーダーが読み終えるまで待ちます。

| スクリーンリーダー | ブラウザーとOS | 下矢印キー | 連続読み上げ |
| --- | --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 | 未確認 |
| TalkBack | Chrome, Android | 未確認 | 未確認 |

https://easeweb.dev/ja/learn/screen-reader-support

---

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