[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects - #9113

Closed
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim
Closed

[WIP] feat(ui): Origin-aware open/close animations for popover menus and selects#9113
maxyinger wants to merge 8 commits into
mainfrom
max/popover-enter-exit-anim

Conversation

@maxyinger

@maxyingermaxyinger commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TODO

  • Some origins are off. needs refinement.

Description

Improves snappyness and adds origin awareness of org switcher, user button, menus & select popover anims.

BeforeAfter
https://github.com/user-attachments/assets/7c236e2e-ad89-48e6-80c8-0eca8ef3b11ehttps://github.com/user-attachments/assets/cfaa57ef-e3bd-4a84-9803-2b8b4a8b2cf6

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:

Summary by CodeRabbit

  • New Features
    • Popover-based surfaces (organization switching, user actions, overflow menus, and selects) now open/close with origin-aware scale-and-fade transitions (entrance plus exit), with softer exits for non-select menus and faster exits for selects.
    • Select dropdowns (including the Phone Input country picker) now support initialScale (with component-specific defaults) and improved exit timing via exitFast.
    • Added new transition timing options: faster duration and swiftOut easing.
  • Bug Fixes
    • Motion effects are disabled when reduced motion is enabled or when appearance animations are turned off.

@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 9, 2026 9:44pm
swingsetReadyReadyPreview, CommentJul 9, 2026 9:44pm

Request Review

@changeset-bot

changeset-botBot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ab7d076

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

This PR includes changesets to release 3 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch
@clerk/swingsetPatch

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

@coderabbitai

coderabbitaiBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Popover surfaces now animate on open and close with motion-aware timing. Menus, selects, and dropdown consumers pass the new animation props through their popover wrappers, and related tests now wait for exit unmounting.

Changes

Popover exit animation

Layer / File(s)Summary
Transition tokens
packages/ui/src/foundations/transitions.ts
Adds faster duration and swiftOut timing values.
Popover exit transitions
packages/ui/src/elements/Popover.tsx
Adds animateExit, exitFast, and initialScale, computes transform origin, gates motion, and renders mounted exit content in portal and non-portal paths.
Consumer wiring and surface updates
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/index.tsx, packages/ui/src/components/UserButton/UserButtonPopover.tsx, packages/ui/src/components/UserButton/index.tsx, packages/ui/src/elements/Menu.tsx, packages/ui/src/elements/Select.tsx, packages/ui/src/elements/PhoneInput/index.tsx, .changeset/popover-enter-exit-animations.md, packages/ui/src/elements/__tests__/Menu.test.tsx
Updates popover consumers to pass animateExit, disables entry animation in OrganizationSwitcher and UserButton, moves menu/select animation styling into Popover props, sets PhoneInput select scaling, documents the change, and updates menu tests for async unmounting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers:LauraBeatris, wobsoriano, iagodahlem

Poem

I thump as popovers softly glide,
Then fade on exit with bunny pride.
New timings hop, new curves take flight,
Reduced motion keeps things right.
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title clearly matches the main change: origin-aware open/close animations for popover menus and selects.
Description check✅ PassedThe description is relevant and accurately describes the popover animation updates in the PR.

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

@pkg-pr-new

pkg-pr-newBot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ab7d076

@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-09T21:45:46.054Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on ab7d076.

@coderabbitaicoderabbitaiBot 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: 1

🧹 Nitpick comments (2)
packages/ui/src/elements/Popover.tsx (2)

60-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded 200 duplicates the theme token instead of referencing it.

The comment claims close "matches the theme's $slow (200ms) transition duration" but hardcodes the literal instead of referencing that token. Drawer.tsx uses a shared transitionDurationValues.drawer constant for the same purpose, avoiding drift if the theme value changes.

♻️ Proposed fix
- duration: { open: 0, close: animateExit && isMotionSafe ? 200 : 0 },+ duration: { open: 0, close: animateExit && isMotionSafe ? transitionDurationValues.slow : 0 },
🤖 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/elements/Popover.tsx` around lines 60 - 72, The Popover
transition duration is hardcoded to 200 instead of using the shared
theme-derived value, which can drift from the actual token. Update the
`useTransitionStyles` config in `Popover` to reference the same shared
transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.

76-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unsafe generic assertion on isValidElement.

React.isValidElement<{ style?: React.CSSProperties }>(children) only confirms children is a valid React element — it doesn't verify the element actually accepts a style prop. If a non-DOM component (e.g. a component without a passthrough style) is ever passed as children with animateExit, the cloned prop would be silently dropped/ignored by that component. Currently safe in practice since consumers only pass DOM-like elements, but worth a comment noting the assumption.

🤖 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/elements/Popover.tsx` around lines 76 - 81, The generic on
React.isValidElement in Popover.tsx is only assuming the child accepts a style
prop, not verifying it. Update the content/cloneElement branch in Popover to add
a clear comment near the React.isValidElement/React.cloneElement logic
explaining that animateExit is only intended for DOM-like children that forward
style, and that non-DOM components may ignore the injected styles.
🤖 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 `@packages/ui/src/elements/Popover.tsx`:
- Around line 105-112: The non-portal branch in Popover is dropping the
outsideElementsInert prop, so it never reaches FloatingFocusManager when portal
is false. Update the Popover rendering path that uses FloatingFocusManager to
pass outsideElementsInert through there as well, matching the portal-based
branch so the prop works consistently in both cases.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 60-72: The Popover transition duration is hardcoded to 200 instead
of using the shared theme-derived value, which can drift from the actual token.
Update the `useTransitionStyles` config in `Popover` to reference the same
shared transition-duration constant/pattern used by `Drawer.tsx` (for the close
duration) so the timing stays aligned with the theme token.
- Around line 76-81: The generic on React.isValidElement in Popover.tsx is only
assuming the child accepts a style prop, not verifying it. Update the
content/cloneElement branch in Popover to add a clear comment near the
React.isValidElement/React.cloneElement logic explaining that animateExit is
only intended for DOM-like children that forward style, and that non-DOM
components may ignore the injected styles.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9439f1f7-4c7a-4777-8c69-277d23109fda

📥 Commits

Reviewing files that changed from the base of the PR and between 08ba540 and 789c9ac.

📒 Files selected for processing (6)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Popover.tsx

Comment threadpackages/ui/src/elements/Popover.tsx
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 789c9ac to 896c8d7CompareJuly 9, 2026 20:06
@maxyingermaxyinger changed the title fix(ui): Rework OrganizationSwitcher and UserButton popover animationsfeat(ui): Origin-aware open/close animations for popover menus and selectsJul 9, 2026
@maxyinger
maxyingerforce-pushed the max/popover-enter-exit-anim branch from 896c8d7 to 3e29ac4CompareJuly 9, 2026 20:11

@coderabbitaicoderabbitaiBot 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.

♻️ Duplicate comments (1)
packages/ui/src/elements/Popover.tsx (1)

152-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

outsideElementsInert still not forwarded on the non-portal path.

This branch was touched again in this PR but still omits outsideElementsInert on FloatingFocusManager, unlike the portal branch (line 140). This was already flagged in a previous review round and remains unresolved.

🤖 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/elements/Popover.tsx` around lines 152 - 165, The non-portal
render path in Popover still drops outsideElementsInert on FloatingFocusManager,
unlike the portal branch. Update the FloatingFocusManager usage in the Popover
component’s shouldRender branch to forward outsideElementsInert from the same
props/state source used in the portal path, keeping the two branches consistent.
🧹 Nitpick comments (1)
packages/ui/src/elements/Popover.tsx (1)

125-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op animation for non-single-element children.

content only applies transitionStyles when children is a single valid element; for arrays/fragments/text it silently falls back to children with no animation and no warning. Currently all call sites pass a single element, so this is low risk today, but it's a fragile contract for future consumers.

🤖 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/elements/Popover.tsx` around lines 125 - 130, The animation
logic in Popover’s content handling only works for a single valid React element,
so non-element children silently skip `transitionStyles` and lose exit
animation. Update the `content`/`React.cloneElement` path to either enforce or
normalize a single animatable child in `Popover` (or document and guard the
contract) so future callers don’t get a no-op when passing fragments, arrays, or
text.
🤖 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.
Duplicate comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 152-165: The non-portal render path in Popover still drops
outsideElementsInert on FloatingFocusManager, unlike the portal branch. Update
the FloatingFocusManager usage in the Popover component’s shouldRender branch to
forward outsideElementsInert from the same props/state source used in the portal
path, keeping the two branches consistent.
---
Nitpick comments:
In `@packages/ui/src/elements/Popover.tsx`:
- Around line 125-130: The animation logic in Popover’s content handling only
works for a single valid React element, so non-element children silently skip
`transitionStyles` and lose exit animation. Update the
`content`/`React.cloneElement` path to either enforce or normalize a single
animatable child in `Popover` (or document and guard the contract) so future
callers don’t get a no-op when passing fragments, arrays, or text.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d5e46c9-c7de-47f7-b21d-8b2787ff307f

📥 Commits

Reviewing files that changed from the base of the PR and between 789c9ac and 896c8d7.

📒 Files selected for processing (10)
  • .changeset/popover-enter-exit-animations.md
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/PhoneInput/index.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Select.tsx
  • packages/ui/src/foundations/transitions.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/popover-enter-exit-animations.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/components/UserButton/index.tsx
  • packages/ui/src/components/UserButton/UserButtonPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx

@maxyingermaxyinger changed the title feat(ui): Origin-aware open/close animations for popover menus and selects[WIP] feat(ui): Origin-aware open/close animations for popover menus and selectsJul 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@maxyinger