_ _ _ _ _ _ _
/ \ ___ ___ ___ ___ ___(_) |__ (_) (_) |_ _ _
/ _ \ / __/ __/ _ \/ __/ __| | '_ \| | | | __| | | |
/ ___ \ (_| (_| __/\__ \__ \ | |_) | | | | |_| |_| |
/_/ \_\___\___\___||___/___/_|_.__/|_|_|_|\__|\__, |
|___/
We are always working to improve the accessibility of our product.
You can view accessibility bugs and improvements that have already been filed using the accessibility label.
The Web Chat team has a comprehensive accessibility test suite, performs thorough manual testing, and also uses Microsoft FastPass to test new features and bug fixes.
To learn more about Assistive Technologies we test and support, please view the technical support guide
We welcome your feedback and will continue to improve the product in this area, as accessibility is one of our top priorities.
We follow WAI-ARIA guidelines on focus management.
- Interactive UI element that can be focused programmatically or via user gesture other than the TAB key, e.g. tap or mouse click
- It is optional to allow TAB key to focus on this element
- A focusable element which can be focused on by pressing TAB key
This is related to #2996.
The user should be able to navigate across multiple activities in the transcript using navigation keys, such as UP, DOWN, HOME and END keys. The navigation keys are based on the WAI-ARIA best practices for grid widget.
Optionally, when the user presses ESCAPE when focused on the transcript, focus will be blurred and sent to the send box.
Although the transcript can be navigated using navigational keys and a keyboard visual indicator is placed around the activity and the transcript, the navigation is not considered a selection (such as aria-selected="true").
Focus redirector is an element to capture and redirect focus, enabling TAB to jump in a specific sequence that would otherwise be impossible to jump using tab sequence in regards to the DOM.
The focus redirector element itself is focusable and invisible. When focused, it will programmatically send the focus to a target element.
They are commonly used in modal dialogs as focus trap, a mechanism to prevent focus from moving outside of the dialog.
Note that on focus, the browser will scroll the focused element into view. This behavior is not preventable (tested through event.preventDefault() on focus event listener in bubbling and capturing phase). Therefore, when placing focus redirector inside a scrollable container, some considerations should be made about its size and position.
While focusing within the transcript and the user presses TAB, it should skip all focusables in the transcript and focus on the next tabbable element after the transcript.
To avoid any DOM elements under a subtree being focusable, HTML introduced a new inert attribute. However, this attribute is not widely adopted. Although polyfill is available, using it may introduce a performance penalty. Thus, we are not using inert attribute at this time.
As a workaround, we added a "terminator" indicator as the last focusable element of the transcript. When the user presses TAB inside the transcript, the focus will land on this element before leaving the transcript. The element is being narrated as "End of transcript". The user will know that they are now at the end of the transcript, and they are expected to press TAB again to focus on the next tabbable element after the transcript.
User can use both TAB and click to focus on the transcript and one of its activities.
The keyboard navigation model is based on WAI-ARIA best practices on managing focus in composites. This model allows two focii (main focus and aria-activedescendant focus) to appear at the same time and is for composite widgets such as combobox, grid, and other complex widgets.
The transcript itself is the main focus, which can be focused by TAB or click. The activity is focused by referencing through the aria-activedescendant attribute on the transcript.
To focus on an activity:
- The user focuses on the transcript by pressing TAB or click
- The default focused activity is the bottommost activity
- The user can press navigational keys to focus on other activities
To focus on tabbable elements in an activity, such as buttons inside an Adaptive Card:
- The user should first focus on the transcript and the activity
- The user should then press ENTER to "enter"/focus on the content
- The first tabbable elements in the activity or attachment will be focused
- After pressing the TAB key on the very last tabbable element in the activity, it should send the focus back to the transcript and the current activity
- Note: we are discussing if this behavior should be changed by focusing on the first tabbable element in any succeeding activities. Please feel free to leave your feedback on the PR for this documentation: #3721
The user should be able to quickly scroll through the transcript using keyboard, in addition to mouse wheel or flick gesture.
Depending on the state of the send box, PAGE UP, PAGE DOWN, HOME and END may be captured by the browser. For example, if the send box is not empty, pressing HOME should move the caret to in front of the first letter.
When the send box is not empty, the navigational keys must not be used for scrolling the transcript.
When the user holds the PAGE UP and PAGE DOWN keys, the transcript should scroll repetitively using the system's keyboard repeat rate and delay.
To scroll up and down, the user should focus on the (empty) send box, then press PAGE UP and PAGE DOWN. Optionally, HOME and END may be used to scroll to the end of the transcript.
This is related to #3135.
The bot sends a question with a set of predefined answers as UI buttons that will drive the conversation towards a particular goal (a.k.a. decision buttons).
After the user makes their decision by clicking on a button, the decision is submitted. The user is not allowed to reselect another decision.
The bot will then send another question with another set of answers.
Once the user makes their selection, we should disable the decision buttons. Since the next question and set of possible decisions do not arrive immediately from the bot, we can not change the focus asynchronously outside of user gestures. Consequently, the user is required to press TAB to move the focus to the next set of decision buttons.
When the user presses the TAB key to move the focus from the current button to the next set of buttons, all the previous decision buttons should be disabled including the button the user chose. This will give a more consistent UX on how buttons are disabled.
Since disabling buttons will also hide them from screen reader, we should add a screen reader-only text to tell the user which answer they chose.
- If there is not a tabbable UI after the current disabled button, the next TAB should move the focus to the send box.
When a UI element is being disabled:
- All UI will be manually styled, based on
:disabled, [aria-disabled="true"]query- User agent stylesheet do not take account into
aria-disabledattribute
- User agent stylesheet do not take account into
- Set
aria-disabledattribute totrue- If the element is a
<button>,onClickis set to a handler that callsevent.preventDefault() - If the element is an
<input type="text">or<textarea>,readOnlyis set totrue
- If the element is a
- Set
tabindexattribute to-1- The element will continue to be focused. But when the focus has moved away, the user can never use the TAB key to move the focus back to the element
If the element is currently focused, the component will wait until theonBlurevent is called to set thedisabledattribute totrueOtherwise, thedisabledattribute will be set totrueimmediately
List of elements support
disabledattribute can be found in this article.
By default, HTML is static. Thus, the default disabled implementation works on a static web page.
On a dynamic web page, when disabled is being applied to a focusing element (document.activeElement), the focus change varies between browsers:
| Browser | Element referenced by document.activeElement |
Element styled by :focus pseudo-class |
Element to focus after pressing TAB |
|---|---|---|---|
| Chrome/Microsoft Edge (Chromium) | Become document.body |
No elements are styled | Next tabbable sibling or descendants of them (depth-first search) |
| Microsoft Edge (Legacy) | Become document.body |
No elements are styled unless <body> is tabbable |
First tabbable descendants of <body> |
| Firefox/Safari | Kept on the disabled element | Styles kept on the disabled element | Next tabbable sibling or descendants of them (depth-first search) |
| Internet Explorer 11 | Become parent container of the disabled element | Parent container of the disabled element | First tabbable descendants of parent container |
On macOS Safari, OPTION + TAB is used to move focus between tabbable elements.
On Firefox and macOS Safari, although disabled button appears to be focusable, they cannot be focused through TAB or JavaScript code.
This is related to #3136.
When the user scrolls up to view past conversation and the bot sends a message with new decision buttons to the user, Web Chat places a "New messages" button on the screen to make the user aware of the new message.
The user should be able to move the focus to the "New messages" button by pressing TAB. Clicking on it will scroll the view to the first decision button and put the focus on it.
If the new message does not contain any tabbable UI, it should move the focus to the send box after clicking on the "New messages" button.
The "New messages" button should be positioned as the first item in the transcript. The transcript is a "composite" widget and is focusable. When the focus in on the transcript, pressing TAB should put the focus on the "New messages" button.
When the "New messages" button is clicked, focus should return to the transcript and put the aria-activedescendant focus on the first new activity, showing a visual keyboard indicator around the activity.
Azure Bot Services is a distributed system and message order is not guaranteed. Web Chat use insertion sort based on the timestamp to order messages.
Messages with a newer timestamp may arrive before messages with an older timestamp. Thus, messages with a newer timestamp could appear on the screen first. Then, messages with an older timestamp will get inserted before it.
Because the time between the insertion is usually very short (adjacent packet in a Web Socket connection), users may not see the insertion visually. But the screen reader always reads the messages in the order they appear on the screen, regardless of their positions in the DOM tree. Thus, the message order could be confusing to users who rely on the screen reader.
We will rectify message order using the replyToId property.
replyToId is a property set by the Bot Framework SDK and it references the activity the bot is replying to. Web Chat uses the replyToId property as a hint when rectifying the message order.
- When a message with a
replyToIdproperty arrives, Web Chat will check if it received the activity with the specified ID:- If an activity was received with the specified ID, Web Chat will render the activity immediately
- If another activity with the same
replyToIdis rendered, Web Chat will render the activity immediately- Another activity with the same
replyToIdmeans, either the predecessor has arrived or declared as lost
- Another activity with the same
- If no activities were received with the specified ID, Web Chat will wait up to 5 seconds for the referencing activity to arrive
- If the activity arrive within 5 seconds, Web Chat will render the activity in the same render loop
- If the activity did not arrive within 5 seconds, Web Chat will render the activity
- When a message without a
replyToIdproperty arrives, or is the first activity in the transcript, Web Chat will render the activity immediately- Currently, there is a limitation in the Bot Framework SDK. The first activity will always comes with a
replyToIdproperty even it is not replying to any conversations
- Currently, there is a limitation in the Bot Framework SDK. The first activity will always comes with a
In a live region, it is difficult to control which part is read or excluded from the screen reader.
Also, browsers and screen readers can be inconsistent on reading the live region. For example:
To make the live region more consistent across browsers and easier to control, we separated the live region from the visible transcript:
- Two copies of transcript
- Visible, rich, dynamic, and interactive transcript
- Screen reader only reads the transcript marked as live region
- Web Chat does not narrate attachment contents: attachments can be customized and the DOM tree could be very complex with interactive elements
- The live region contains activities that were recently
- When the DOM element appear in the live region, the screen reader will compute the alternative text and queue it for narration in a first-come-first-serve manner
- The screen reader will keep the alternative text in the queue even after the DOM element is removed from the live region
- One second after the activity is rendered in the live region, Web Chat will remove it from the live region. This has a few benefits:
- Workaround some browser and screen reader bugs that may keep repeating the entire transcript
- The screen reader users will not be able to navigate into it and they will not notice there are 2 copies of the transcript
- If the removal is too fast:
- 0-100 ms: Chrome and TalkBack on Android may miss some of the activities
- The development team settled on using one second after some experimentation
- Activity Time Stamp Announcement : Related to #3136
- Problem definition : Even when a user overrides the 'grouptimestamp' props and sets it to true or to some interval, AT still announces every activity with it's associated Time stamp.
- Explanation of current behavior : Once activity is marked as sent, it is written to DOM as well as it's Timestamp; the timestamp grouping logic is executed only when the next activity arrives. As mention earlier once a text is queued for narration and even if DOM element is removed it will still be announced by the screen reader as it is not technically possible to removed from the narration queue.
- Given above limitation even if we removed the timestamp element from DOM after group timestamp logic is executed this will not change the screen reader behavior.
- As per Accessibility team review/recommendation : There is no hiding or loss of information in this case - so will keep the current behavior as is.
- After an element is removed from tab order, put the focus on next logical tabbable element
- "New messages" button
- After clicking on the "New messages" button, the focus should move to the first tabbable element of all the unread activities or the send box as last resort
- Related to #3136
- Suggested actions buttons
- After clicking on any suggested action buttons, the focus should move to the send box without soft keyboard
- For better UX, the activity asking the question should have the answer inlined
- Related to #3135
- "New messages" button
- Don't use numbers other than
0or-1intabindexattribute- This will pollute the hosting environment
- Do not use
tabindex="-1"in DOM nodes that don't need it, otherwise, it will be focusable by mouse
- In an activity with question and answers, after clicking on a decision button, don't disable the button
- When the user reads the activity, the screen reader will only read the question but not the chosen answer
- It is okay to disable buttons that were not chosen as answer
- It is okay to disable all buttons, as long as the answer will be read by the screen reader
- Related to #3135
- Don't move focus when an activity arrives (or asynchronously)
- Screen reader reading will be interrupted when focus changes
- Only change focus synchronous to user gesture
- Related to #3135
Web Chat render components are accompanied by a screen reader renderer to maximize accessibility. In the case of custom components, the bot/Web Chat developer will need to implement a screen reader renderer for the equivalent custom visual component.
The Web Chat team DOES NOT recommend disabling warning messages regarding screen readers and accessibility. However, if the developer decides to suppress these messages, it can be done by adding the following code to attachmentForScreenReaderMiddleware in the Composer props.
const attachmentForScreenReaderMiddleware = () => next => () => {
return false;
};This will prevent the screen reader renderer warning from appearing in the browser console.

