Skip to content

Show Copy confirmation for 5 seconds and keep focus indicator visible… - #5872

Merged
Eugene (OEvgeny) merged 5 commits into
mainfrom
OEvgeny/fix-5857-copy-confirmation
Oct 16, 2026
Merged

Eugene (OEvgeny) merged 5 commits into
mainfrom
OEvgeny/fix-5857-copy-confirmation

Conversation

@OEvgeny

@OEvgeny Eugene (OEvgeny) commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #5857

Changelog Entry

  • Fixed the "Copied" confirmation of the Copy button disappearing too quickly: it is now shown for 5 seconds, fixes #5857, in PR #5872, by @OEvgeny

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:

  • covered the focus indicator of the button;
  • produced the accessible name "Copy Copied", which Narrator reads as Copy Copied, button, then Copy, button half 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):

Before After
"Copied" visible ~0.5s (Fluent) / ~0.7s (default) 5.0s in all themes
Focus indicator while "Copied" Hidden by the overlay Visible
Speech per press Copied → Copy Copied, button → Copy, button Copied → Copied, button → Copy, button after 5s
Pressing again while "Copied" Restarts the animation, name churn Restarts the 5s, no extra speech

Out 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 animationend removed the class. That cannot guarantee a minimum duration:

  • Hiding Web Chat restarts it. When Web Chat is hidden while "Copied" is shown, the CSS animation is canceled without animationend, so the class stays and the animation replays when Web Chat is shown again. Chromium does not fire animationcancel here: verified in Chrome 141 and Edge 155.
  • Reduced-motion resets defeat it. A common host reset (animation-duration: 0.01ms !important under prefers-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 manual classList toggling 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, classList or 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.png updated: the only difference is the now-visible focus indicator.

  • I have added tests and executed them locally

  • I have updated CHANGELOG.md

  • I have updated documentation

Review Checklist

This section is for contributors to review your work.

  • Accessibility reviewed (tab order, content readability, alt text, color contrast)
  • Browser and platform compatibilities reviewed
  • CSS styles reviewed (minimal rules, no z-index)
  • Documents reviewed (docs, samples, live demo)
  • Internationalization reviewed (strings, unit formatting)
  • package.json and package-lock.json reviewed
  • Security reviewed (no data URIs, check for nonce leak)
  • Tests reviewed (coverage, legitimacy)

…#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.
Comment thread packages/component/src/Attachment/Text/private/ActivityCopyButton.tsx Outdated
Comment thread CHANGELOG.md Outdated
Co-authored-by: Eugene <EOlonov@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 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.

Comment thread CHANGELOG.md Outdated
@OEvgeny

Copy link
Copy Markdown
Collaborator Author

This is a successor to and closes #5864

Comment thread CHANGELOG.md Outdated
Comment thread __tests__/html2/copyButton/behavior.hideAndShow.html Outdated
Comment thread __tests__/html2/copyButton/behavior.hideAndShow.html Outdated
Comment thread __tests__/html2/copyButton/behavior.html Outdated
…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.
@ahonamatata

Copy link
Copy Markdown
Contributor

As an upstream Web Chat patch, this PR is well engineered. It fixes several concrete Web Chat defects together:

  • Removes unreliable CSS animation cleanup.

  • Removes the Fluent-specific 500 ms duration.

  • Keeps Copied visible for five seconds.

  • Produces the clean accessible name Copied, rather than Copy Copied.

  • Keeps the focus indicator visible.

  • Uses deterministic fake-clock tests.

  • Adds reusable accessibility-tree test helpers.

It is simpler and better aligned with the Web Chat team’s design preferences.

@OEvgeny
Eugene (OEvgeny) merged commit f81b9d8 into main Oct 16, 2026
32 checks passed
@OEvgeny
Eugene (OEvgeny) deleted the OEvgeny/fix-5857-copy-confirmation branch October 16, 2026 00:01
Eugene (OEvgeny) added a commit that referenced this pull request Oct 16, 2026
* 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>
Eugene (OEvgeny) pushed a commit that referenced this pull request Oct 18, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants