Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Legends:
- Fixed the keyboard focus indicator of the "Previous" and "Next" flipper buttons of suggested actions in carousel layout: they now show a visible focus ring, fixes [#5725](https://github.com/microsoft/BotFramework-WebChat/issues/5725), in PR [#5878](https://github.com/microsoft/BotFramework-WebChat/pull/5878), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed card images with empty alternate text (`altText: ""` in Adaptive Cards or `alt: ""` in rich cards) not being rendered as decorative images, related to [#5747](https://github.com/microsoft/BotFramework-WebChat/issues/5747), in PR [#5877](https://github.com/microsoft/BotFramework-WebChat/pull/5877), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed the keyboard focus indicator of the "Previous" and "Next" flipper buttons in carousel layout: they now show a visible focus ring, and the focused flipper is shown even at the start or end of the carousel, fixes [#5704](https://github.com/microsoft/BotFramework-WebChat/issues/5704) and [#5744](https://github.com/microsoft/BotFramework-WebChat/issues/5744), in PR [#5876](https://github.com/microsoft/BotFramework-WebChat/pull/5876), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed the keyboard focus indicator of card action buttons, such as hero card buttons, having less than 3:1 contrast against a pressed button: it is now a two-tone ring that is visible against both the button and the card, fixes [#5742](https://github.com/microsoft/BotFramework-WebChat/issues/5742), in PR [#5879](https://github.com/microsoft/BotFramework-WebChat/pull/5879), by [@OEvgeny](https://github.com/OEvgeny)

## [4.19.2] - 2026-10-08

Expand Down
11 changes: 6 additions & 5 deletions __tests__/assets/focusManagement.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
:focus {
/* Card action buttons draw their own keyboard focus indicator, so this file does not override it. */
:focus:not(.ac-pushButton:focus-visible) {
outline-color: rgba(0, 0, 0, 0.4) !important;
outline-offset: -4px !important;
outline-style: dotted !important;
outline-width: 2px !important;
}

:focus:not(.webchat__scroll-to-end-button) {
:focus:not(.ac-pushButton:focus-visible, .webchat__scroll-to-end-button) {
background-color: Yellow !important;
}

:disabled:focus,
[aria-disabled='true']:focus {
:disabled:focus:not(.ac-pushButton:focus-visible),
[aria-disabled='true']:focus:not(.ac-pushButton:focus-visible) {
outline-color: rgba(0, 0, 0, 0.2) !important;
}

Expand All @@ -20,7 +21,7 @@
color: White !important;
}

#webchat .card__action--performed:focus {
#webchat .card__action--performed:focus:not(:focus-visible) {
outline-color: rgba(255, 255, 255, 0.6) !important;
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Comment thread
compulim marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/carousel/navigation.tab.cardInput.html.snap-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Comment thread
compulim marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-1.png

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the new styling didn't help here much.

@OEvgeny Eugene (OEvgeny) Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These buttons are not pressed. The browser ring was already 2px near-black on white here (19:1), and the button border is also black, so the new 2px ring looks about the same. The fix is for colored buttons: on pressed blue, the old ring was 2.76:1. I tried a thicker ring inside the border, but it looked blurry at fractional display scaling, so the ring stays 2px on the edge, like our other buttons.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/focusManagement/preserve.html.snap-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<script>
location = './heroCard.pushButton.focusVisible?variant=fluent';
</script>
</head>
<body></body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions __tests__/html2/richCard/heroCard.pushButton.focusVisible.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for",
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
"botframework-webchat/": "/__dist__/packages/bundle/static/botframework-webchat/",
"botframework-webchat-fluent-theme": "/__dist__/packages/fluent-theme/static/botframework-webchat-fluent-theme.js",
"react": "/__dist__/packages/bundle/static/react.js",
"react-dom": "/__dist__/packages/bundle/static/react-dom.js",
"react-dom/client": "/__dist__/packages/bundle/static/react-dom/client.js"
}
}
</script>
<script type="module">
import '/test-harness.mjs';
import '/test-page-object.mjs';

import { waitFor } from '@testduet/wait-for';
import { createStoreWithOptions, ReactWebChat } from 'botframework-webchat';
import { FluentThemeProvider } from 'botframework-webchat-fluent-theme';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';

// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
window.WebChat = { createStoreWithOptions };

run(async function () {
const { directLine, store } = testHelpers.createDirectLineEmulator();
const variant = new URLSearchParams(location.search).get('variant');
const webChatElement = createElement(ReactWebChat, { directLine, store });

createRoot(document.getElementById('webchat')).render(
variant ? createElement(FluentThemeProvider, { variant }, webChatElement) : webChatElement
);

await pageConditions.uiConnected();

// Same as the "carousel" command of the mock bot.
await directLine.emulateIncomingActivity({
attachmentLayout: 'carousel',
attachments: [1, 2, 3].map(index => ({
content: {
buttons: [
{ title: 'Places To Buy', type: 'imBack', value: 'Place to buy' },
{ title: 'Related Products', type: 'imBack', value: 'Related Products' }
],
subtitle: 'This is the subtitle',
text: 'Price: $XXX.XX USD',
title: `Details about image ${index}`
},
contentType: 'application/vnd.microsoft.card.hero'
})),
from: { role: 'bot' },
type: 'message'
});

await pageConditions.numActivitiesShown(1);

const placesToBuyButton = document.querySelector('.carousel-layout .ac-pushButton');

// WHEN: Focus on the first attachment of the carousel via keyboard.
pageObjects.focusTranscript();
await host.sendKeys('ENTER');

// WHEN: Press TAB to move focus to the "Places To Buy" button.
await host.sendTab();

// THEN: The "Places To Buy" button should be focused.
expect(document.activeElement).toBe(placesToBuyButton);
expect(placesToBuyButton.textContent).toBe('Places To Buy');

// THEN: The button should show a two-tone focus indicator: a black ring and a white inner band.
await pageConditions.scrollStabilized();
await host.snapshot('local');

// WHEN: Press ENTER to press the "Places To Buy" button.
(await directLine.actPostActivity(() => host.sendKeys('ENTER'))).resolveAll();

await pageConditions.numActivitiesShown(2);
await pageConditions.allOutgoingActivitiesSent();

// THEN: The "Places To Buy" button should be pressed and keep the focus.
await waitFor(() => expect(placesToBuyButton.getAttribute('aria-pressed')).toBe('true'));
expect(document.activeElement).toBe(placesToBuyButton);

// THEN: The white inner band should separate the black ring from the pressed button (blue).
await pageConditions.scrollToBottomCompleted();
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is thicker, black border in white label. It's inconsistent with the above blue border on active button. We should reconcile favoring this one.

Seems:

disableHeroCard.obsolete.html.snap-2 <— thinner, didn't modify HTML it seems
heroCard.pushButton.focusVisible.html <— new test, thicker, but no CSS override

@OEvgeny Eugene (OEvgeny) Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this is the real style. The test HTML was not changed. The thinner look came from the shared test stylesheet focusManagement.css, which overrides outline with !important. It now skips keyboard-focused card buttons, so the 5 affected snapshots show this same ring.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type StrictAdaptiveCardsStyleOptions = {
cardPushButtonBackgroundColor: string | undefined;

/**
* Adaptive Cards: text color of Adaptive Cards button with status of 'aria-pressed'
* Adaptive Cards: text color of Adaptive Cards button with status of 'aria-pressed', and the inner band of the keyboard focus indicator of Adaptive Cards buttons
*/
cardPushButtonTextColor: string | undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@
padding: var(--webchat__padding--regular);
}

/* Two-tone focus indicator, drawn inside the button as the action set clips anything outside of the button. */
/* The outer ring is visible against the card, the inner band separates it from the button, including when pressed. */
:global(.ac-pushButton:focus-visible) {
/* The inset shadow starts inside the 1px border, so the band is 2px wide inside the outer ring. */
box-shadow: inset 0 0 0 calc(var(--webchat__border-width--transcript-visual-keyboard-indicator) + 1px)
var(--webchat__color--card-push-button, white);
outline: var(--webchat__border-width--transcript-visual-keyboard-indicator)
var(--webchat__border-style--transcript-visual-keyboard-indicator)
var(--webchat__color--transcript-visual-keyboard-indicator);
outline-offset: calc(-1 * var(--webchat__border-width--transcript-visual-keyboard-indicator));
}

:global(.ac-pushButton.style-destructive) {
background-color: #e50000;
color: white;
Expand Down
Loading