Skip to content

feat(ui): announce the user button's in-flight action - #9404

Closed
alexcarpenter wants to merge 3 commits into
mainfrom
carp/mosaic-user-button-pending-announcement
Closed

feat(ui): announce the user button's in-flight action#9404
alexcarpenter wants to merge 3 commits into
mainfrom
carp/mosaic-user-button-pending-announcement

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Aug 11, 2026

Copy link
Copy Markdown
Member

Description

A row in the Mosaic user button that starts an action keeps focus while it stands down, and its spinner is decorative, so nothing reported the wait to assistive tech: aria-disabled says a row cannot be used, not why.

The popup now carries a polite live region from the moment it opens, and names the running action in it ("Switching to Foundry") for as long as it runs.

  • The wording comes from userButtonBase.status, like every other string the surface renders, so it localizes with the rest.
  • The label is derived from pendingKey and the rows' own names, keyed through userButtonBusyKeys rather than parsed, so the key grammar stays in one place and a container announces without passing anything extra.
  • One region on the surface rather than one per affordance: pendingKey already allows a single action at a time.
  • It lives on the popup, not in the acting row: the row often goes when the action lands (the list re-sorts, a signed-out account leaves), which would unmount the region mid-announcement.
  • It is mounted while idle and empty: a region that arrives with its message already in it is not announced.

A key the surface cannot name (a row that has already left the list) leaves the region silent rather than announcing a half-filled template.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

The row that starts an action keeps focus while it stands down, and its spinner is decorative, so
nothing reported the wait. The popup now carries a polite live region from the moment it opens, and
`pendingLabel` names the running action for it to speak.
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f300e5c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 11, 2026 9:45pm
swingset Ready Ready Preview Aug 11, 2026 9:45pm

Request Review

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-11T21:46:03.523Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on f300e5c.

@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9404

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9404

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9404

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9404

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9404

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9404

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9404

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9404

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9404

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9404

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9404

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9404

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9404

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9404

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9404

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9404

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9404

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9404

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9404

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9404

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9404

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9404

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9404

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9404

commit: f300e5c

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a36393f-2dac-4779-aed4-64daa8b1f625

📥 Commits

Reviewing files that changed from the base of the PR and between a7bed1c and f300e5c.

📒 Files selected for processing (2)
  • packages/ui/src/mosaic/user-button/__tests__/user-button.view.test.tsx
  • packages/ui/src/mosaic/user-button/user-button.view.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/mosaic/user-button/user-button.view.tsx

📝 Walkthrough

Walkthrough

UserButton now generates localized announcements for pending account, organization, sign-out, invitation, and suggestion actions. ActionStatus renders each announcement in an always-mounted, visually hidden role="status" region. Tests cover named actions, idle state, closed-popup persistence, missing targets, and organization loading status containment.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: maxyinger

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: announcing the user button's in-flight action.
Description check ✅ Passed The description explains the accessible live-region announcements and directly matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/ui/src/mosaic/user-button/__tests__/user-button.view.test.tsx (1)

731-738: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test a live-region update after mount.

The test mounts pendingLabel in the first render. It only checks DOM text. It does not verify that an existing role='status' element receives the later label. Render the idle state first, keep the status element, then rerender with the pending label.

As per coding guidelines, unit tests are required for all new functionality. The PR objective requires a persistent live region to announce the in-flight action.

Proposed test update
 it('reports the wait in a live region', () => {
-  render(surface(userButtonBusyKeys.switchSession('sess_2'), { pendingLabel: 'Switching to Bob Jones' }));
+  const pendingLabel = 'Switching to Bob Jones';
+  const { rerender } = render(surface(null, { pendingLabel }));
+  const status = screen.getByRole('status');
+  expect(status).toBeEmptyDOMElement();
+
+  rerender(surface(userButtonBusyKeys.switchSession('sess_2'), { pendingLabel }));

-  expect(screen.getByRole('status')).toHaveTextContent('Switching to Bob Jones');
+  expect(status).toHaveTextContent(pendingLabel);
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/user-button/__tests__/user-button.view.test.tsx`
around lines 731 - 738, Update the reports-the-wait-in-a-live-region test to
render the idle state first, retain the existing role="status" element, then
rerender with userButtonBusyKeys.switchSession('sess_2') and the pendingLabel.
Assert the persistent status element receives the label after the update rather
than only verifying text from the initial mount.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/mosaic-user-button-pending-announcement.md:
- Around line 1-2: Add a Changeset entry for the publicly published `@clerk/ui`
package, specifying the appropriate version bump and a release note describing
the exported UserButtonBusyState change.

In `@packages/ui/src/mosaic/user-button/user-button.view.tsx`:
- Around line 998-1008: Update ActionStatus to render pendingLabel only when
pendingKey is present, treating null or absent pendingKey as idle and rendering
an empty status region. Keep the mounted region and existing pendingLabel
behavior unchanged while an action is active.

---

Nitpick comments:
In `@packages/ui/src/mosaic/user-button/__tests__/user-button.view.test.tsx`:
- Around line 731-738: Update the reports-the-wait-in-a-live-region test to
render the idle state first, retain the existing role="status" element, then
rerender with userButtonBusyKeys.switchSession('sess_2') and the pendingLabel.
Assert the persistent status element receives the label after the update rather
than only verifying text from the initial mount.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7011c16e-9cb7-487c-b0ef-327e72f2c851

📥 Commits

Reviewing files that changed from the base of the PR and between 4c23729 and f4103a4.

📒 Files selected for processing (5)
  • .changeset/mosaic-user-button-pending-announcement.md
  • packages/ui/src/mosaic/user-button/__tests__/user-button.view.test.tsx
  • packages/ui/src/mosaic/user-button/user-button.styles.ts
  • packages/ui/src/mosaic/user-button/user-button.types.ts
  • packages/ui/src/mosaic/user-button/user-button.view.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment on lines +1 to +2
---
---

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

package_json="$(fd --type f --max-depth 1 '^package\.json$' packages/ui | head -n1)"
test -n "$package_json"
jq '{name, private}' "$package_json"

Repository: clerk/javascript

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- packages/ui/package.json ---'
cat packages/ui/package.json

printf '%s\n' '--- changeset configuration ---'
fd --type f --max-depth 2 '^(config\.json|package\.json)$' .changeset packages | sort | head -40
if test -f .changeset/config.json; then
  cat .changeset/config.json
fi

printf '%s\n' '--- workspace package metadata ---'
rg -n '"`@clerk/ui`"|packages/ui|private|publishConfig' package.json pnpm-workspace.yaml turbo.json .changeset packages --glob 'package.json' --glob 'pnpm-workspace.yaml' --glob 'turbo.json' --glob '!**/node_modules/**' | head -160

Repository: clerk/javascript

Length of output: 9775


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- target changeset ---'
cat .changeset/mosaic-user-button-pending-announcement.md

printf '%s\n' '--- UserButtonBusyState definitions and references ---'
rg -n -C 3 'UserButtonBusyState' packages

printf '%s\n' '--- current change summary ---'
git diff --stat
git diff --unified=3 -- packages/ui packages/react packages/clerk-js .changeset/mosaic-user-button-pending-announcement.md | head -240

Repository: clerk/javascript

Length of output: 2744


Add a Changeset entry for @clerk/ui

@clerk/ui is publicly published. Add the appropriate version bump and release note for the exported UserButtonBusyState change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/mosaic-user-button-pending-announcement.md around lines 1 - 2,
Add a Changeset entry for the publicly published `@clerk/ui` package, specifying
the appropriate version bump and a release note describing the exported
UserButtonBusyState change.

Sources: Coding guidelines, Learnings

Comment thread packages/ui/src/mosaic/user-button/user-button.view.tsx
The announcement was a raw `pendingLabel` prop, so the one string assistive tech actually hears was
the one string the surface did not own and the localization layer could never reach. The view now
derives it from `pendingKey` and the rows' own names, out of `userButtonBase.status`.
Comment on lines +8 to +15
visuallyHidden: {
overflow: 'hidden',
clipPath: 'inset(50%)',
position: 'absolute',
whiteSpace: 'nowrap',
height: '1px',
width: '1px',
},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

probably should lift to an atom in the future to reuse.

Picking a workspace closes the popup behind it, so a region living in the popup was taken off the
page while it was still being read out. It moves to the root, which stays as long as the surface.
@alexcarpenter

Copy link
Copy Markdown
Member Author

closing, gonna explore an alternate route.

@alexcarpenter
alexcarpenter deleted the carp/mosaic-user-button-pending-announcement branch August 11, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants