Repository navigation
Show Copy confirmation for 5 seconds and keep focus indicator visible… - #5872
Conversation
…#5857) The "Copied" confirmation was driven by a CSS animation of 0.7s (0.5s with the Fluent theme), too short to be perceived. It also covered the focus indicator and produced the accessible name "Copy Copied". - Drive the copied state with React state and a ponyfill timer (5 seconds), activating again restarts it. Replaces the CSS animation, which restarted after Web Chat was hidden and shown, and the manual classList toggling. - Hide the "Copy" label and icon with selectors that match <ActivityButton>, so the accessible name reads "Copied" while confirmed. - Make the "Copied" overlay transparent so the focus indicator stays visible. - Remove the Fluent theme 500ms duration override. - Tests use a fake clock: shown at 4.999s, hidden at 5s, restart on re-activation, hide/show, and accessible names.
Co-authored-by: Eugene <EOlonov@gmail.com>
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues remain; the changelog link correction is non-blocking.
1 open finding
What changed in this PR
Improves Copy confirmation accessibility for #5857 by keeping “Copied” visible for five seconds without covering the focus indicator.
Changes:
- Replaces animation-driven confirmation with a restartable React timer.
- Corrects label visibility and removes Fluent timing overrides.
- Adds timing, reactivation, hide/show, and accessible-name assertions.
| File | Description |
|---|---|
| packages/fluent-theme/src/components/theme/Theme.module.css | Removes confirmation background and duration overrides. |
| packages/component/src/Attachment/Text/private/ActivityCopyButton.tsx | Manages confirmation with state and a five-second timer. |
| packages/component/src/Attachment/Text/private/ActivityCopyButton.module.css | Preserves focus visibility and hides the original label during confirmation. |
| CHANGELOG.md | Documents the confirmation fix. |
| tests/html2/copyButton/behavior.html | Checks timing, reactivation, and accessible names. |
| tests/html2/copyButton/behavior.hideAndShow.html | Checks confirmation timing across hiding and showing. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
This is a successor to and closes #5864 |
Fix PR #
…e/show - Add testHelpers.getAccessibilityNode(element) and testHelpers.getAccessibleName(element), use them in copyButton/behavior.html and accessibility/messageActions.html (no more data-a11y-* markers). - behavior.hideAndShow.html: query the Copy button after hiding and showing Web Chat instead of reusing the old element. - Remove the checkAccessibility workaround: the button name is never empty now (Copy -> Copied), axe passes on every snapshot.
8a6a038
|
As an upstream Web Chat patch, this PR is well engineered. It fixes several concrete Web Chat defects together:
It is simpler and better aligned with the Web Chat team’s design preferences. |
* Move changelog entries merged after 4.19.2 to Unreleased #5865 and #5872 were merged after v4.19.2 was published (2026-10-09T05:20Z), but their entries were filed under 4.19.2. * Fix flaky scrollToTop and unknownAttachment tests - pageObjects.scrollToTop(): react-scroll-to-bottom ignores scroll events right after its animation ends without updating the sizes it remembers. The next scroll is mistaken as a resize while sticky and it scrolls back to the bottom. Scroll again (up to 3 attempts), like a user would. Fixes scrollToEndButton.containerSize.html failing ~15-25% under load (40/40 after). - unknownAttachment.html: the live region renders the activity 5-15 ms after the transcript, wait for the console.warn before asserting. * Remove checkAccessibility overrides from tests Replacing window.checkAccessibility disabled accessibility checks for whole tests. With the checker running: - 11 tests had obsolete overrides (no violations in any variant), removed. part-grouping/keyboard, navigation and position never ran with a variant, the overrides were dead code. - Known violations are now skipped explicitly with skipCheckAccessibility, scoped to the failing variants and snapshots, with a TODO: - part-grouping/status and index (copilot): part grouping remounts when its first activity changes, aria-activedescendant points to the removed group. - side-by-side: the deprecated Copilot custom variant puts non-articles in role="feed"; contrast in the View code dialog. - markdown/vnext/markdownStreaming: bot markdown "# Heading" renders a level-one heading (#4721). * Address review: use waitFor, link react-scroll-to-bottom fix - unknownAttachment.html: wait with @testduet/wait-for and expect. - scrollToTop(): clarify why scrolling again is needed, add a TODO to remove the retry after compulim/react-scroll-to-bottom#145 is released. --------- Co-authored-by: Eugene Olonov <eugeneolonov@microsoft.com>
Moves the 5 second timer of the "Copied" confirmation (#5872) out of ActivityCopyButton, so the copy button of code blocks can share it. The ponyfill is passed in, because that button renders outside of Web Chat's React tree.

Changelog Entry
Description
After pressing Copy, "Copied" was visible for 0.7 seconds, or 0.5 seconds with the Fluent theme, too short to be perceived. While it was shown, it also:
Copy Copied, button, thenCopy, buttonhalf a second later.This PR keeps "Copied" for 5 seconds, keeps the focus indicator visible, and makes the accessible name a clean "Copied" while confirmed.
Measured on the same page and actions (Edge 155 + Narrator, Fluent theme):
Copied→Copy Copied, button→Copy, buttonCopied→Copied, button→Copy, buttonafter 5sOut of scope: in hosts where Web Chat's live region is not exposed (see the comment on #5857), the "Copied" announcement is not spoken. Making announcements independent of the host page (e.g.
ariaNotify) is a separate change.Design
The confirmation was driven by a CSS animation whose
animationendremoved the class. That cannot guarantee a minimum duration:animationend, so the class stays and the animation replays when Web Chat is shown again. Chromium does not fireanimationcancelhere: verified in Chrome 141 and Edge 155.animation-duration: 0.01ms !importantunderprefers-reduced-motion) would remove it immediately.The copied state is now React state, set on activation and cleared by a
usePonyfill()timer (5 seconds). Activating again restarts the timer. The CSS is static. This also removes the manualclassListtoggling and the reflow trick.The rule that was meant to hide the "Copy" label and icon while confirmed never matched: the CSS module scoped selectors for elements rendered by
<ActivityButton>. It now uses global selectors, so the label is hidden and the accessible name becomes "Copied". The overlay no longer paints a background, so the focus indicator is not covered.The Fluent theme override that shortened the duration to
--webchat-durationUltraSlow(500ms) is removed.Specific Changes
ActivityCopyButton.tsx: React state +usePonyfill()timer (5s constant); no CSS animation,animationend,classListor reflow; function component.ActivityCopyButton.module.css: static copied state; label and icon hidden via selectors that match<ActivityButton>; no overlay background; keyframes removed.Fluent
Theme.module.css: removed the copy button duration and background overrides.__tests__/html2/copyButton/behavior.html,behavior.hideAndShow.html: fake clock via ponyfill. Asserts "Copied" at 4.999s and hidden at 5s, restart on re-activation, hide/show does not extend or replay, and the accessible names "Copy" → "Copied" → "Copy".behavior.html.snap-2.pngupdated: the only difference is the now-visible focus indicator.I have added tests and executed them locally
I have updated
CHANGELOG.mdI have updated documentation
Review Checklist
z-index)package.jsonandpackage-lock.jsonreviewed