# Help chatbots with no way to reach a person

Source: https://easeweb.dev/learn/human-help
Topics: Menus and navigation > A help chatbot also offers a way to reach a person (best practice; related to WCAG 3.2.6)
The fix: Offer a person in the chat itself, from the first message. Either add a Talk to a person button that opens a short message form for the team and sends the conversation with it, or, as the smallest change, put a staff email address and a reply time in the chat's footer. Either way, the bot's reply when it doesn't understand names that route too.
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

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

Birchwood Home, an online furniture shop, answers questions with a chatbot, Ask Birchwood, in the same corner of every page. It knows three topics, tracking, returns and delivery times, which cover most of what customers ask, and it never closes. A customer's new sofa arrives with one leg missing. They open the chat and write "My sofa came with one leg missing". The bot replies "Sorry, I didn't get that. Try a topic below." Track my order says the order was delivered on Thursday 22 October, which they know. Returns explains how to send the sofa back, which they don't want. They try "I need a new leg for my sofa", then "can I talk to someone", and get the same reply each time. The help page lists the same three topics, and nowhere on the site is there an email address, a form or a phone number.

The same dead end turns up in other forms:

- A way to reach a person that appears only after the bot has failed several times. Someone the bot misunderstands, rather than fails to understand, never sees it: a message such as "My delivery is missing a leg" gets the delivery times, and to the bot that was an answer.
- A Contact us link in the footer that opens the same bot.

## Why it fails

No success criterion asks for a way to reach a person, and this page passes the one closest to it. Success criterion 3.2.6, Consistent Help, counts a chatbot as help, "a fully automated contact mechanism", and asks only that help repeated across pages sits in the same place on each of them. Ask Birchwood does. Its Understanding page says the criterion doesn't require a site to offer help at all, nor a person to be available at all times. So this guide is a best practice: it shows what the criterion's own guidance recommends, not a failure of it.

That guidance is plain about bots. The same Understanding page says chatbots can work for many people, especially people with cognitive disabilities, if they recognise misspelled words, give human contact details when they can't answer satisfactorily after three attempts, and can be closed in one step. W3C's cognitive accessibility guidance has a pattern for it, Provide Human Help: when someone gets stuck, a person is usually the most effective help, and a route to one that only people who can manage a complex system can reach shuts out the people who need it most.

The bot works as it was built. It looks for one of its words in the message, and anything without one gets the fallback. The fallback points back to the same three topics, so a question outside them goes round in a loop, and nothing in the panel leads anywhere else. Each reply, read alone, is polite and correct.

## Who is affected

The questions a bot can't answer are the ones with no standard answer: a missing part, a charge that looks wrong, a delivery that didn't happen. They are also the questions that most need a person, and people who already find writing or typing hard meet the loop first.

## What automation and AI miss

No axe rule looks at what a chat says, and EaseWeb's audit has none either. A scan doesn't hold a conversation, the widget often runs in a vendor's frame, and whether anyone reads what is sent is a business arrangement no page shows. The audit's lead for chat widgets sends a person to the chat to check that its messages are read aloud; that is the moment to look for a way to a person too. A code assistant asked to build a support bot can write a fallback like the one here, listing the topics again, unless it is asked for a way to a person.

## Before: a chatbot that only lists its topics

```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;
}
…
```

The chat itself is built well: its messages are a named log, its field is labelled and its topics are buttons. What it lacks is any way past them. A question without one of its three words gets the fallback, the fallback points back at the same topics, and nothing in the panel leads to a person.

## After: a Talk to a person button in the chat

```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.';
});
```

The button is in the panel from the first message, under the topics, so nobody has to fail first to find it, and the fallback names it at the moment the bot gives up. It opens a form of two fields, the kind of short route the cognitive accessibility guidance suggests, says who replies and when, and sends the conversation with it, so nobody has to tell the story twice. The status line says it was sent. The form posts to a staff inbox; a button that opens a person route nobody reads fails the same people, only later.

## After: an email address in the chat, the smallest change

```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;
}
…
```

One line under the message field, in the panel from the first message, and the fallback names the same address. The address is the link's text, not "Email us", because a mailto: link may open nothing on a computer with no mail app set up, and then the address has to be read and copied. The subject is filled in, so the team knows which order it is about. It works only while someone reads that inbox and replies in the time the line promises.

## Which option to use

Both put a person in the chat from the first message. The Talk to a person button keeps people in the chat, sends the conversation along, and needs no mail app, but it needs a form that posts somewhere and a team that reads it: choose it when you can change the chat, or the vendor offers a hand-over to staff. The email line is one line that any chat's footer, or the page beside it, can carry: choose it when the widget is a vendor's and its settings stop there.

## Implementation decisions

Offering a person only after the bot has failed is a common middle ground, and Understanding 3.2.6 describes it, with three attempts. Treat that as the least a bot should do, not the design. A bot can't tell when its answer was wrong: delivery times given to a question about a missing leg are an answer to it, so a counter of failed replies never counts them. The cognitive accessibility guidance lists forcing people through several steps to reach a person among the things to avoid.

Each route to a person needs the same care in a different way:

- A chat with staff, handed over from the bot: say so when it happens, and when no one is online, offer the form or email address in its place, never a "no agents available" message on its own.
- A message form: two or three fields, a reply time, and a status message that says it was sent, as in the first fix. No CAPTCHA in front of it.
- An email address or phone number: written out as text, with a reply time or opening hours. A phone number alone is not enough, as [support that can only be reached by phone](/learn/support-contact) explains.

A bot built on a language model may not say it didn't understand. It can answer fluently and wrongly, so a fallback that offers a person may never appear. The way to a person has to be in the panel from the start for these bots too.

Call it what it is. "Talk to a person" or "Email our team" is plain; "Escalate", "Live agent" or an icon of a headset is not. Some bots also answer "person", "human" or "agent" typed in the message field by offering the same route, which helps, but only beside the button or line, since people who don't know to type those words never find it.

Every other door to help should lead to the same people. A Contact us link that opens the bot again is the second form of this failure. Wherever the chat and the way to a person sit, keep them in the same place on every page, as [help links that move from page to page](/learn/consistent-help) explains. The chat's launcher and close button, and whether its messages are read aloud, are covered in [chat buttons that Tab skips, and close buttons with no name](/learn/chat-launchers) and [chat widgets that never announce their messages](/learn/chat-widgets).

## Verify the fix

1. Open the chat and, before typing anything, find the way to a person in the panel.
2. Ask the bot something outside its topics, in your own words and with a spelling mistake, and confirm its reply names the way to a person.
3. Send a test message by each route to a person and confirm a person replies within the time the chat promises, without sending you back to the bot.
4. With a keyboard alone, and with a screen reader, reach the way to a person, send a message by it, and hear that it was sent.
5. Follow every Contact us, Help and Support link on the site and confirm none of them opens the bot as the only way to ask.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after any change to the bot's topics, the widget or the support team's hours.

## Limits

This is a starting pattern, not a guarantee for every site. A way to reach a person helps only as far as the people behind it do: a reply that takes a week, or a team that can't change an order, fails the same customers later. This guide is about whether a person can be reached, not about how the bot itself is built; test the whole route, from the first question to the reply, with the people your customers include.

## What screen readers do (tested by hand by EaseWeb)

`aria-expanded`: Says whether a button’s menu or section is open.

| Screen reader | Browser and system | Press it | Tab to it |
| --- | --- | --- | --- |
| JAWS | Chrome, Windows 11 | Not tested | Not tested |
| NVDA | Chrome, Windows 11 | Not tested | Not tested |
| JAWS | Edge, Windows 11 | Not tested | Not tested |
| NVDA | Firefox, Windows 11 | Not tested | Not tested |
| NVDA | Edge, Windows 11 | Not tested | Not tested |
| JAWS | Firefox, Windows 11 | Not tested | Not tested |
| VoiceOver | Safari, macOS | Not tested | Not tested |
| VoiceOver | Safari, iOS | Not tested | Not tested |
| TalkBack | Chrome, Android | Not tested | Not tested |

`role="status"`: A polite live region: its changes wait until the screen reader has finished what it is saying.

| Screen reader | Browser and system | Down Arrow | Say all |
| --- | --- | --- | --- |
| JAWS | Chrome, Windows 11 | Not tested | Not tested |
| NVDA | Chrome, Windows 11 | Not tested | Not tested |
| JAWS | Edge, Windows 11 | Not tested | Not tested |
| NVDA | Firefox, Windows 11 | Not tested | Not tested |
| NVDA | Edge, Windows 11 | Not tested | Not tested |
| JAWS | Firefox, Windows 11 | Not tested | Not tested |
| VoiceOver | Safari, macOS | Not tested | Not tested |
| VoiceOver | Safari, iOS | Not tested | Not tested |
| TalkBack | Chrome, Android | Not tested | Not tested |

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

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
