Search topics
Chat buttons that Tab skips, and close buttons with no name
The chat launcher is a div with a click handler, so Tab skips it, and the panel's × is a button holding only an icon, so a screen reader hears just "button".
<div class="chat-launcher"> The problem
Keyboard users can't open the support chat, and screen reader users aren't told which button closes it.
Birchwood Home, a furniture shop, has a round chat bubble in the bottom corner of every page. A mouse user clicks it, and a chat panel opens in its place: a heading, Chat with Birchwood, a × to close it, the greeting, and a message field. A keyboard user presses Tab down the page, through Delivery prices, Track an order and Returns, and the next Tab leaves the page for the browser's own controls. The bubble is never reached, and Enter can't open what focus never lands on. A screen reader user who opens the chat some other way and tabs to the × hears "button" and nothing else: not what it does, nor that it closes the chat.
The bubble is a div with a click listener and an icon in it. A div takes no focus, so Tab goes past it, and Enter and Space do nothing to it. Success criterion 2.1.1 Keyboard asks that everything a mouse can do on the page can be done with a keyboard, and opening the chat is something only a mouse can do here.
Success criterion 4.1.2 Name, Role, Value asks that every control has a name and a role that assistive technology can read. The bubble has neither: it is not a button, and it has no words, only a drawing. The × is a real button, so it has the role, and Tab reaches it, but all it holds is an icon with no text, so its name is empty and a screen reader announces just "button". In a panel with a Send button beside it, there is no telling which is which without pressing them. W3C lists both as failures of 4.1.2: a div made into a control by a script, with no role (F59), and a control with no name (F68).
The chat sits in the same corner on every page, and it is often where a site sends people who are stuck, so the ones who can't open it lose the help that was meant for them.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard users, and switch users whose switches send Tab and Enter, never reach the chat bubble, so they can't open the chat at all. Voice control users have no name to say for the bubble or for the ×.
- Without vision Screen reader users don't hear the bubble as a button, so they may never know the site has a chat. If they open it, the × is announced only as "button", with nothing to say that it closes the chat.
A scan sees the bubble as a div with a drawing in it. The listener that opens the chat is added by a script, so nothing in the markup says the div is a control; only its pointer cursor and its place, fixed in the corner, give it away. The × is easier to catch, because an empty button is a common check, but only while the panel is open, and most chat panels are closed when the page loads and stay closed unless something opens them. Chat widgets are often a vendor's script, sometimes drawn inside a frame from the vendor's domain, so the site's own team rarely reads the code. AI assistants asked for a chat bubble can write a clickable div, because it looks and works the same with a mouse.
Try it
Open the chat and close it again with the keyboard alone, and hear what each button is called.
Tab down the page: does focus ever reach the chat bubble? Click the bubble to open the chat, then Tab to the × and read what a screen reader says under the page.
Apply a fix and do it all with the keyboard.
Broken Can you fix this?
Delivery
Most orders arrive within five working days. Large items are delivered on a day you choose.
Chat with Birchwood
Hi! How can we help with your order?
Screen reader says:
(Tab through the page)
The fix
Make the launcher and the × native buttons, and give each a name in words.
- 1The chat launcher is a native button that Tab reaches.
- 2The launcher and the close button each have a name in words, and their icons are hidden.
- 3Closing the chat puts focus back on the launcher.
<div class="chat-launcher"> <svg class="icon" viewBox="0 0 24 24"> <use href="#icon-chat"></use> </svg></div><section id="chat" class="chat-panel" aria-labelledby="chat-title" hidden> <div class="chat-header"> <h2 id="chat-title">Chat with Birchwood</h2> <button type="button" class="chat-close"> <svg class="icon" viewBox="0 0 24 24"> <use href="#icon-close"></use> </svg> </button> </div> <div class="chat-messages" role="log" aria-label="Messages"> <p class="message from-us">Hi! How can we help with your order?</p> </div> <form class="chat-form"> <label for="chat-input" class="visually-hidden">Message</label> <input id="chat-input" autocomplete="off"> <button>Send</button> </form></section>Named buttons, the round icon kept
<button type="button" class="chat-launcher" aria-label="Chat with us"> <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"> <use href="#icon-chat"></use> </svg></button><section id="chat" class="chat-panel" aria-labelledby="chat-title" hidden> <div class="chat-header"> <h2 id="chat-title">Chat with Birchwood</h2> <button type="button" class="chat-close" aria-label="Close chat"> <svg class="icon" viewBox="0 0 24 24" aria-hidden="true"> <use href="#icon-close"></use> </svg> </button> </div> <div class="chat-messages" role="log" aria-label="Messages"> <p class="message from-us">Hi! How can we help with your order?</p> </div> <form class="chat-form"> <label for="chat-input" class="visually-hidden">Message</label> <input id="chat-input" autocomplete="off"> <button>Send</button> </form></section>Why this fix: Use native HTML first
The click listener on the div works for a mouse only. The × is a button with nothing in it but the svg, which has no title, so the button has no name. Even launcher.focus() in closeChat() does nothing to a div, and focus falls back to the page.
The launcher is now a button: Tab reaches it, Enter and Space press it, the same click listener runs, and closing the chat puts focus back on it. aria-label gives the launcher the name "Chat with us" and the × the name "Close chat", so they are announced as "Chat with us, button" and "Close chat, button", and aria-hidden keeps the icons out of what is read. The script doesn't change. The button keeps the div's class, so it keeps its look; if the browser's own border or background shows through, clear them in that class. Visually hidden text inside each button names it just as well, as icon-only buttons with no accessible name shows.
Both make the launcher and the × buttons that Tab reaches, and both name them, so both pass 2.1.1 and 4.1.2. A hidden name keeps the round bubble as drawn, and takes the least space on a phone, where the launcher sits over the page. A visible label costs width, but everyone sees what the button is for: a voice control user says the words on it rather than guessing its hidden name, and a person who doesn't know the icon reads the word. Choose the visible label when the design has room for it, and the hidden name when the bubble must stay small.
Most chat widgets are a vendor's script, and the launcher and panel are often drawn inside a frame from the vendor's domain, where your own CSS and scripts can't reach. What you can do.
- Test the widget before you choose it, with the steps under "Verify the fix", on the vendor's own demo page or a test page of yours.
- Check the widget's settings. Some let you show a text label on the launcher, and some let you set the names of its buttons; set both, in each language your site uses.
- Ask the vendor whether the launcher and the close button are native buttons with names, and which screen readers they tested with. Ask too for the frame's title, which is the subject of iframes with a missing or generic title.
- Report what fails to the vendor, with the steps to reproduce it. Until it is fixed, list it as a known issue in your accessibility statement, and give another way to ask for help, such as an email address, on the page where the chat is offered.
Vendor scripts change without a deploy on your side, so repeat the checks after each vendor update and each change to the widget's settings.
role="button" on the div. Adding the role makes a screen reader say "button", but the div still takes no focus and ignores Enter and Space until you add tabindex="0" and a key handler for both. The native button does all of that already; div buttons that can't be reached with Tab shows why the role on its own is not a fix.
Name it after what it does. "Chat with us", "Help" or "Ask a question" says what happens; "Chat icon", "Bubble" or the vendor's product name doesn't. The × is "Close chat", not "×" or "Close icon". If the launcher shows a count of unread replies, put it in the name too, "Chat with us, 1 new message", as chat widgets that never announce their messages suggests for replies that arrive while the panel is closed.
A panel that also has a minimise button. Some panels have both a minimise and a close button. Name each after what it does ("Minimise chat", "End chat"), since two unnamed icons side by side are worse than one.
Where the launcher sits. A launcher pinned to the corner can cover the control that has focus, as in floating chat buttons that cover part of keyboard focus, and a panel written after the footer is reached last, as in floating buttons and panels that keyboard focus reaches last. A panel that holds focus and leaves the keyboard no way out, with no close control that Tab reaches and no Escape, is a keyboard trap: see pop-ups that can't be closed with a keyboard.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Speech input
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the chat widget, its vendor's script or its settings change.
Limits
This is a starting pattern for a launcher and a panel drawn by the page, not a guarantee for every widget. Whether a vendor's widget can be changed at all depends on the vendor; inside a frame from another domain, only its settings and the vendor can change it. Announcing the chat's messages is a separate failure, covered in chat widgets that never announce their messages, and a chat that is only a bot, with no way through to a person, in help chatbots with no way to reach a person. Test the complete journey with the assistive technology your visitors use.
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.
EaseWeb service
Fixed it? Keep it from coming back.
I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.