Add screen-reader copy success announcements#6
Conversation
Reviewer's GuideImplements a reusable, timed screen-reader announcement pattern for copy-to-clipboard actions, wires it into key copy flows, and updates the toast utility to support accessible aria options without duplicating spoken content. Sequence diagram for copy success screen-reader announcement flowsequenceDiagram
actor User
participant Component as CopyComponent
participant Hook as useCopySuccessAnnouncement
participant LiveRegion as CopySuccessAnnouncement
participant ToastHelper as showToast
participant ToastLib as react_hot_toast
participant Clipboard as navigator.clipboard
User->>Component: Click copy button
Component->>Clipboard: writeText(value)
Clipboard-->>Component: Promise resolved
Component->>Hook: announceCopySuccess(message)
Hook-->>Hook: setAnnouncement("")
Note over Hook: 25ms delay
Hook-->>Hook: setAnnouncement(message)
Hook-->>LiveRegion: announcement prop updated
LiveRegion-->>User: [aria-live polite announcement]
Component->>ToastHelper: success("Copied to clipboard", { ariaProps: COPY_SUCCESS_TOAST_ARIA_PROPS })
ToastHelper->>ToastLib: toast.success(message, options)
ToastLib-->>User: Visible toast (aria-live off)
Note over Hook: After clearAfterMs
Hook-->>Hook: setAnnouncement("")
Hook-->>LiveRegion: message cleared
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- You now mount a separate
CopySuccessAnnouncement+useCopySuccessAnnouncementpair in multiple components; consider lifting this to a shared parent (or context/provider) so you have a single live region per page and less duplicated wiring. - All the copy flows pass hard-coded English strings into
announceCopySuccess; if you plan to localize or standardize these messages, it may be cleaner to centralize them as constants or derive them from context instead of inlining per component.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You now mount a separate `CopySuccessAnnouncement` + `useCopySuccessAnnouncement` pair in multiple components; consider lifting this to a shared parent (or context/provider) so you have a single live region per page and less duplicated wiring.
- All the copy flows pass hard-coded English strings into `announceCopySuccess`; if you plan to localize or standardize these messages, it may be cleaner to centralize them as constants or derive them from context instead of inlining per component.
## Individual Comments
### Comment 1
<location path="src/hooks/useCopySuccessAnnouncement.ts" line_range="10" />
<code_context>
+ ToastOptions['ariaProps']
+> = {
+ role: 'status',
+ 'aria-live': 'off',
+};
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `aria-live="off"` on a `role="status"` region is likely both semantically wrong and a TypeScript type mismatch with `ToastOptions['ariaProps']`.
`ToastOptions['ariaProps']` usually types `aria-live` as `'polite' | 'assertive' | undefined`, so `'off'` may not be allowed and could trigger TS errors. It also conflicts with `role="status"`, which implies a polite live region. If you want this toast not to announce, either drop both `role` and `aria-live`, or (if types permit) keep `aria-live="off"` without `role="status"`, and reserve `role="status"` for the dedicated announcement element.
</issue_to_address>
### Comment 2
<location path="src/components/common/TransactionHashRow.tsx" line_range="24-27" />
<code_context>
const [copied, setCopied] = useState(false);
+ const { announcement, announceCopySuccess } = useCopySuccessAnnouncement();
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation();
await navigator.clipboard.writeText(hash);
+ announceCopySuccess('Transaction hash copied.');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
</code_context>
<issue_to_address>
**issue (bug_risk):** Copy handler doesn’t handle clipboard failures but still announces success to the user.
If `navigator.clipboard.writeText(hash)` throws (permissions, unsupported env, etc.), we’ll still announce success and set `copied` to `true`, which is misleading. Consider wrapping this in a try/catch, only calling `announceCopySuccess` and updating `copied` on success, and handling the error path similarly to `TransactionFailureDrawer` (e.g., error toast).
</issue_to_address>
### Comment 3
<location path="src/components/common/TruncatedAddress.tsx" line_range="32-34" />
<code_context>
const [copied, setCopied] = useState(false);
+ const { announcement, announceCopySuccess } = useCopySuccessAnnouncement();
const handleCopy = async () => {
await navigator.clipboard.writeText(address);
+ announceCopySuccess('Address copied.');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
</code_context>
<issue_to_address>
**issue (bug_risk):** Address copy handler also assumes clipboard writes always succeed but announces success unconditionally.
Since `navigator.clipboard.writeText(address)` can fail, this should be wrapped in a try/catch. On error, avoid setting `copied` to `true` and either skip the success announcement or show an error toast instead, so users don’t get misleading feedback.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| ToastOptions['ariaProps'] | ||
| > = { | ||
| role: 'status', | ||
| 'aria-live': 'off', |
There was a problem hiding this comment.
issue (bug_risk): Using aria-live="off" on a role="status" region is likely both semantically wrong and a TypeScript type mismatch with ToastOptions['ariaProps'].
ToastOptions['ariaProps'] usually types aria-live as 'polite' | 'assertive' | undefined, so 'off' may not be allowed and could trigger TS errors. It also conflicts with role="status", which implies a polite live region. If you want this toast not to announce, either drop both role and aria-live, or (if types permit) keep aria-live="off" without role="status", and reserve role="status" for the dedicated announcement element.
| const handleCopy = async (e: React.MouseEvent) => { | ||
| e.stopPropagation(); | ||
| await navigator.clipboard.writeText(hash); | ||
| announceCopySuccess('Transaction hash copied.'); |
There was a problem hiding this comment.
issue (bug_risk): Copy handler doesn’t handle clipboard failures but still announces success to the user.
If navigator.clipboard.writeText(hash) throws (permissions, unsupported env, etc.), we’ll still announce success and set copied to true, which is misleading. Consider wrapping this in a try/catch, only calling announceCopySuccess and updating copied on success, and handling the error path similarly to TransactionFailureDrawer (e.g., error toast).
| const handleCopy = async () => { | ||
| await navigator.clipboard.writeText(address); | ||
| announceCopySuccess('Address copied.'); |
There was a problem hiding this comment.
issue (bug_risk): Address copy handler also assumes clipboard writes always succeed but announces success unconditionally.
Since navigator.clipboard.writeText(address) can fail, this should be wrapped in a try/catch. On error, avoid setting copied to true and either skip the success announcement or show an error toast instead, so users don’t get misleading feedback.
Motivation
Description
useCopySuccessAnnouncementwithCOPY_SUCCESS_TOAST_ARIA_PROPSto manage timed, repeat-safe live-region announcements (src/hooks/useCopySuccessAnnouncement.ts).CopySuccessAnnouncementthat renders the announcement for screen readers (src/components/common/CopySuccessAnnouncement.tsx).TransactionHashRow,TruncatedAddress,TransactionFailureDrawer,CreatorProfileHeader, andEmptyTransactionTimelineStateso copy actions callannounceCopySuccessand expose the live region without showing extra visible text.showToast(src/utils/toast.util.tsx) to acceptreact-hot-toastoptions so copy-success toasts can be emitted with `aria-live=Codex Task
Summary by Sourcery
Introduce a reusable, screen-reader-friendly copy success announcement pattern and apply it across existing copy-to-clipboard flows.
New Features:
Bug Fixes:
Enhancements:
Tests: