Skip to content

Conversation

@alexcarpenter
Copy link
Member

@alexcarpenter alexcarpenter commented Nov 25, 2025

Description

The <UNSAFE_PortalProvider> component allows you to specify a custom container for Clerk floating UI elements (popovers, modals, tooltips, etc.) that use portals. Only Clerk components within the provider will be affected—components outside the provider will continue to use the default document.body for portals.

This is particularly useful when using Clerk components inside external UI libraries like Radix Dialog or React Aria Components, where portaled elements need to render within the dialog's container to remain interactable.

Example usage

'use client'

import { useRef } from 'react'
import * as Dialog from '@radix-ui/react-dialog'
import { UNSAFE_PortalProvider, UserButton } from '@clerk/nextjs'

export function UserDialog() {
  const containerRef = useRef<HTMLDivElement>(null)

  return (
    <Dialog.Root>
      <Dialog.Trigger>Open Dialog</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content ref={containerRef}>
          <UNSAFE_PortalProvider getContainer={() => containerRef.current}>
            <UserButton />
          </UNSAFE_PortalProvider>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

Docs: clerk/clerk-docs#2896

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

    • Introduced PortalProvider component for customizing where modals, tooltips, and popovers render across React, Vue, Astro, Expo, and Next.js packages.
    • Added getContainer prop support to modal components for specifying custom rendering containers.
  • Tests

    • Added comprehensive test coverage for PortalProvider functionality across frameworks.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Nov 25, 2025

🦋 Changeset detected

Latest commit: fa6ef65

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

This PR includes changesets to release 19 packages
Name Type
@clerk/shared Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/localizations Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch

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

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

@vercel
Copy link

vercel bot commented Nov 25, 2025

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

1 Skipped Deployment
Project Deployment Review Updated (UTC)
clerk-js-sandbox Skipped Skipped Dec 17, 2025 2:15pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

📝 Walkthrough

Walkthrough

Introduces portal container customization via UNSAFE_PortalProvider (React/Vue) and usePortalRoot hook across multiple packages, enabling consumers to specify custom containers for floating UI elements. Updates modal type definitions with optional getContainer prop, integrates container resolution in UI components, and re-exports new APIs through package entry points.

Changes

Cohort / File(s) Summary
Core Portal Provider Implementation
packages/shared/src/react/PortalProvider.tsx, packages/vue/src/components/PortalProvider.ts, packages/vue/src/composables/usePortalRoot.ts
New PortalProvider component and usePortalRoot hook for React and Vue; React version uses context helper, Vue uses injection pattern; both memoize/provide getContainer function to descendants with fallback null behavior
Portal Keys & Type Exports
packages/vue/src/keys.ts, packages/shared/src/react/index.ts, packages/vue/src/composables/index.ts
New PortalInjectionKey for Vue injection system; re-exports UNSAFE_PortalProvider and usePortalRoot from core modules into public API
Shared Type Definitions
packages/shared/src/types/clerk.ts
Extends 7 modal prop types (SignInModal, SignUpModal, UserProfile, OrganizationProfile, CreateOrganization, Waitlist, UserVerification) with optional getContainer?: () => HTMLElement | null property
React & Shared Package Re-exports
packages/react/src/contexts/index.ts, packages/react/src/components/withClerk.tsx
Re-exports UNSAFE_PortalProvider from @clerk/shared/react; updates withClerk HOC to inject getContainer prop via usePortalRoot hook
Framework Integration Re-exports
packages/nextjs/src/client-boundary/controlComponents.ts, packages/nextjs/src/index.ts, packages/expo/src/index.ts, packages/astro/src/react/index.ts, packages/nuxt/src/runtime/components/index.ts, packages/react-router/src/client/index.ts, packages/tanstack-react-start/src/client/index.ts, packages/vue/src/components/index.ts
Re-exports UNSAFE_PortalProvider and usePortalRoot across framework-specific package entry points
Modal & Portal Rendering Integration
packages/ui/src/Components.tsx, packages/ui/src/lazyModules/providers.tsx
Wraps modals with UNSAFE_PortalProvider; forwards getContainer from modal state to LazyModalRenderer; adds getContainer prop to 7 modal instances (SignIn, SignUp, UserProfile, UserVerification, OrganizationProfile, CreateOrganization, Waitlist)
Floating Portal Element Integration
packages/ui/src/elements/Modal.tsx, packages/ui/src/elements/Popover.tsx, packages/ui/src/elements/Drawer.tsx, packages/ui/src/elements/Tooltip.tsx
Updates FloatingPortal root resolution to prefer explicit prop, fallback to usePortalRoot context, then undefined; computes effectivePortalRoot and passes to portal root prop
Feature Component Integration
packages/ui/src/components/UserButton/useMultisessionActions.tsx, packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx, packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx, packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx, packages/ui/src/components/PricingTable/PricingTable.tsx
Integrates usePortalRoot hook to propagate getContainer into openUserProfile, openOrganizationProfile, openCreateOrganization, and openSignIn calls; adds test coverage for PortalProvider integration
Vue Component Integration
packages/vue/src/components/ClerkHostRenderer.ts
Merges getContainer from usePortalRoot into props for mounting, opening, and updating modal renderer
Style & Test Updates
packages/ui/src/elements/Menu.tsx, packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx, packages/shared/src/react/__tests__/PortalProvider.test.tsx, packages/vue/src/composables/__tests__/usePortalRoot.test.ts
Adds white-space: nowrap to MenuItem; comprehensive test coverage for PortalProvider context behavior, nested provider precedence, and out-of-context fallback
Version Metadata
.changeset/wet-phones-camp.md
Patch version bump for @clerk/shared

Sequence Diagram(s)

sequenceDiagram
    participant Consumer
    participant PortalProvider
    participant usePortalRoot
    participant Modal/Component
    participant FloatingPortal

    Consumer->>PortalProvider: Wrap component with UNSAFE_PortalProvider(getContainer)
    PortalProvider->>PortalProvider: Create context with getContainer function
    Consumer->>Modal/Component: Render child component
    Modal/Component->>usePortalRoot: Call hook to retrieve portal container
    usePortalRoot->>PortalProvider: Inject/consume PortalContext
    usePortalRoot-->>Modal/Component: Return getContainer function
    Modal/Component->>FloatingPortal: Pass getContainer result as root prop
    FloatingPortal->>FloatingPortal: Render floating UI in custom container
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A portal appears, so fine and neat,
Where floating elements find their seat!
Custom containers, oh what a feat,
Portal providers make UI complete!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main feature added across the PR: introducing the UNSAFE_PortalProvider component. This aligns with the changeset's core objective of enabling custom portal containers for Clerk floating UI elements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version

Tip: Use the snippet copy button below to quickly install the required packages.

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version

Tip: Use the snippet copy button below to quickly install the required packages.

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.2.6-snapshot.v20251125180024
@clerk/astro 2.16.4-snapshot.v20251125180024
@clerk/backend 2.24.1-snapshot.v20251125180024
@clerk/chrome-extension 2.8.7-snapshot.v20251125180024
@clerk/clerk-js 5.111.1-snapshot.v20251125180024
@clerk/elements 0.23.87-snapshot.v20251125180024
@clerk/clerk-expo 2.19.7-snapshot.v20251125180024
@clerk/expo-passkeys 0.4.23-snapshot.v20251125180024
@clerk/express 1.7.54-snapshot.v20251125180024
@clerk/fastify 2.6.6-snapshot.v20251125180024
@clerk/localizations 3.28.6-snapshot.v20251125180024
@clerk/nextjs 6.35.6-snapshot.v20251125180024
@clerk/nuxt 1.13.4-snapshot.v20251125180024
@clerk/clerk-react 5.57.1-snapshot.v20251125180024
@clerk/react-router 2.3.1-snapshot.v20251125180024
@clerk/remix 4.13.21-snapshot.v20251125180024
@clerk/shared 3.36.1-snapshot.v20251125180024
@clerk/tanstack-react-start 0.27.6-snapshot.v20251125180024
@clerk/testing 1.13.20-snapshot.v20251125180024
@clerk/themes 2.4.41-snapshot.v20251125180024
@clerk/types 4.101.4-snapshot.v20251125180024
@clerk/vue 1.17.1-snapshot.v20251125180024

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.2.6-snapshot.v20251125180024 --save-exact

@clerk/astro

npm i @clerk/astro@2.16.4-snapshot.v20251125180024 --save-exact

@clerk/backend

npm i @clerk/backend@2.24.1-snapshot.v20251125180024 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@2.8.7-snapshot.v20251125180024 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@5.111.1-snapshot.v20251125180024 --save-exact

@clerk/elements

npm i @clerk/elements@0.23.87-snapshot.v20251125180024 --save-exact

@clerk/clerk-expo

npm i @clerk/clerk-expo@2.19.7-snapshot.v20251125180024 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@0.4.23-snapshot.v20251125180024 --save-exact

@clerk/express

npm i @clerk/express@1.7.54-snapshot.v20251125180024 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.6.6-snapshot.v20251125180024 --save-exact

@clerk/localizations

npm i @clerk/localizations@3.28.6-snapshot.v20251125180024 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@6.35.6-snapshot.v20251125180024 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@1.13.4-snapshot.v20251125180024 --save-exact

@clerk/clerk-react

npm i @clerk/clerk-react@5.57.1-snapshot.v20251125180024 --save-exact

@clerk/react-router

npm i @clerk/react-router@2.3.1-snapshot.v20251125180024 --save-exact

@clerk/remix

npm i @clerk/remix@4.13.21-snapshot.v20251125180024 --save-exact

@clerk/shared

npm i @clerk/shared@3.36.1-snapshot.v20251125180024 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@0.27.6-snapshot.v20251125180024 --save-exact

@clerk/testing

npm i @clerk/testing@1.13.20-snapshot.v20251125180024 --save-exact

@clerk/themes

npm i @clerk/themes@2.4.41-snapshot.v20251125180024 --save-exact

@clerk/types

npm i @clerk/types@4.101.4-snapshot.v20251125180024 --save-exact

@clerk/vue

npm i @clerk/vue@1.17.1-snapshot.v20251125180024 --save-exact

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.2.9-snapshot.v20251215203425
@clerk/astro 3.0.0-snapshot.v20251215203425
@clerk/backend 3.0.0-snapshot.v20251215203425
@clerk/chrome-extension 3.0.0-snapshot.v20251215203425
@clerk/clerk-js 6.0.0-snapshot.v20251215203425
@clerk/dev-cli 1.0.0-snapshot.v20251215203425
@clerk/expo 3.0.0-snapshot.v20251215203425
@clerk/expo-passkeys 1.0.0-snapshot.v20251215203425
@clerk/express 2.0.0-snapshot.v20251215203425
@clerk/fastify 2.6.9-snapshot.v20251215203425
@clerk/localizations 4.0.0-snapshot.v20251215203425
@clerk/nextjs 7.0.0-snapshot.v20251215203425
@clerk/nuxt 2.0.0-snapshot.v20251215203425
@clerk/react 6.0.0-snapshot.v20251215203425
@clerk/react-router 3.0.0-snapshot.v20251215203425
@clerk/shared 4.0.0-snapshot.v20251215203425
@clerk/tanstack-react-start 1.0.0-snapshot.v20251215203425
@clerk/testing 2.0.0-snapshot.v20251215203425
@clerk/ui 1.0.0-snapshot.v20251215203425
@clerk/upgrade 2.0.0-snapshot.v20251215203425
@clerk/vue 2.0.0-snapshot.v20251215203425

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251215203425 --save-exact

@clerk/astro

npm i @clerk/astro@3.0.0-snapshot.v20251215203425 --save-exact

@clerk/backend

npm i @clerk/backend@3.0.0-snapshot.v20251215203425 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215203425 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.0.0-snapshot.v20251215203425 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@1.0.0-snapshot.v20251215203425 --save-exact

@clerk/expo

npm i @clerk/expo@3.0.0-snapshot.v20251215203425 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215203425 --save-exact

@clerk/express

npm i @clerk/express@2.0.0-snapshot.v20251215203425 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.6.9-snapshot.v20251215203425 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.0.0-snapshot.v20251215203425 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.0.0-snapshot.v20251215203425 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.0.0-snapshot.v20251215203425 --save-exact

@clerk/react

npm i @clerk/react@6.0.0-snapshot.v20251215203425 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.0.0-snapshot.v20251215203425 --save-exact

@clerk/shared

npm i @clerk/shared@4.0.0-snapshot.v20251215203425 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215203425 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.0-snapshot.v20251215203425 --save-exact

@clerk/ui

npm i @clerk/ui@1.0.0-snapshot.v20251215203425 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.0-snapshot.v20251215203425 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.0-snapshot.v20251215203425 --save-exact

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.2.9-snapshot.v20251215210631
@clerk/astro 3.0.0-snapshot.v20251215210631
@clerk/backend 3.0.0-snapshot.v20251215210631
@clerk/chrome-extension 3.0.0-snapshot.v20251215210631
@clerk/clerk-js 6.0.0-snapshot.v20251215210631
@clerk/dev-cli 1.0.0-snapshot.v20251215210631
@clerk/expo 3.0.0-snapshot.v20251215210631
@clerk/expo-passkeys 1.0.0-snapshot.v20251215210631
@clerk/express 2.0.0-snapshot.v20251215210631
@clerk/fastify 2.6.9-snapshot.v20251215210631
@clerk/localizations 4.0.0-snapshot.v20251215210631
@clerk/nextjs 7.0.0-snapshot.v20251215210631
@clerk/nuxt 2.0.0-snapshot.v20251215210631
@clerk/react 6.0.0-snapshot.v20251215210631
@clerk/react-router 3.0.0-snapshot.v20251215210631
@clerk/shared 4.0.0-snapshot.v20251215210631
@clerk/tanstack-react-start 1.0.0-snapshot.v20251215210631
@clerk/testing 2.0.0-snapshot.v20251215210631
@clerk/ui 1.0.0-snapshot.v20251215210631
@clerk/upgrade 2.0.0-snapshot.v20251215210631
@clerk/vue 2.0.0-snapshot.v20251215210631

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251215210631 --save-exact

@clerk/astro

npm i @clerk/astro@3.0.0-snapshot.v20251215210631 --save-exact

@clerk/backend

npm i @clerk/backend@3.0.0-snapshot.v20251215210631 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215210631 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.0.0-snapshot.v20251215210631 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@1.0.0-snapshot.v20251215210631 --save-exact

@clerk/expo

npm i @clerk/expo@3.0.0-snapshot.v20251215210631 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215210631 --save-exact

@clerk/express

npm i @clerk/express@2.0.0-snapshot.v20251215210631 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.6.9-snapshot.v20251215210631 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.0.0-snapshot.v20251215210631 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.0.0-snapshot.v20251215210631 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.0.0-snapshot.v20251215210631 --save-exact

@clerk/react

npm i @clerk/react@6.0.0-snapshot.v20251215210631 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.0.0-snapshot.v20251215210631 --save-exact

@clerk/shared

npm i @clerk/shared@4.0.0-snapshot.v20251215210631 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215210631 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.0-snapshot.v20251215210631 --save-exact

@clerk/ui

npm i @clerk/ui@1.0.0-snapshot.v20251215210631 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.0-snapshot.v20251215210631 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.0-snapshot.v20251215210631 --save-exact

@alexcarpenter
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.2.9-snapshot.v20251215212157
@clerk/astro 3.0.0-snapshot.v20251215212157
@clerk/backend 3.0.0-snapshot.v20251215212157
@clerk/chrome-extension 3.0.0-snapshot.v20251215212157
@clerk/clerk-js 6.0.0-snapshot.v20251215212157
@clerk/dev-cli 1.0.0-snapshot.v20251215212157
@clerk/expo 3.0.0-snapshot.v20251215212157
@clerk/expo-passkeys 1.0.0-snapshot.v20251215212157
@clerk/express 2.0.0-snapshot.v20251215212157
@clerk/fastify 2.6.9-snapshot.v20251215212157
@clerk/localizations 4.0.0-snapshot.v20251215212157
@clerk/nextjs 7.0.0-snapshot.v20251215212157
@clerk/nuxt 2.0.0-snapshot.v20251215212157
@clerk/react 6.0.0-snapshot.v20251215212157
@clerk/react-router 3.0.0-snapshot.v20251215212157
@clerk/shared 4.0.0-snapshot.v20251215212157
@clerk/tanstack-react-start 1.0.0-snapshot.v20251215212157
@clerk/testing 2.0.0-snapshot.v20251215212157
@clerk/ui 1.0.0-snapshot.v20251215212157
@clerk/upgrade 2.0.0-snapshot.v20251215212157
@clerk/vue 2.0.0-snapshot.v20251215212157

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251215212157 --save-exact

@clerk/astro

npm i @clerk/astro@3.0.0-snapshot.v20251215212157 --save-exact

@clerk/backend

npm i @clerk/backend@3.0.0-snapshot.v20251215212157 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.0.0-snapshot.v20251215212157 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.0.0-snapshot.v20251215212157 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@1.0.0-snapshot.v20251215212157 --save-exact

@clerk/expo

npm i @clerk/expo@3.0.0-snapshot.v20251215212157 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251215212157 --save-exact

@clerk/express

npm i @clerk/express@2.0.0-snapshot.v20251215212157 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.6.9-snapshot.v20251215212157 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.0.0-snapshot.v20251215212157 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.0.0-snapshot.v20251215212157 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.0.0-snapshot.v20251215212157 --save-exact

@clerk/react

npm i @clerk/react@6.0.0-snapshot.v20251215212157 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.0.0-snapshot.v20251215212157 --save-exact

@clerk/shared

npm i @clerk/shared@4.0.0-snapshot.v20251215212157 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251215212157 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.0-snapshot.v20251215212157 --save-exact

@clerk/ui

npm i @clerk/ui@1.0.0-snapshot.v20251215212157 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.0-snapshot.v20251215212157 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.0-snapshot.v20251215212157 --save-exact

@alexcarpenter alexcarpenter changed the title feat(clerk-js): PortalProvider POC feat(clerk-js): Introduce UNSAFE_PortalProvider Dec 15, 2025
@alexcarpenter alexcarpenter marked this pull request as ready for review January 8, 2026 15:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.changeset/wet-phones-camp.md:
- Around line 1-3: The changeset currently declares a patch release for
'@clerk/shared' but you added a new public export (UNSAFE_PortalProvider);
update the changeset header from 'patch' to 'minor' so the release semver
reflects a new public API export; edit the top YAML block in
.changeset/wet-phones-camp.md and replace 'patch' with 'minor' for the
'@clerk/shared' entry.

In @packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx:
- Around line 34-56: The test description for APIKeyModal in
APIKeyModal.test.tsx claims scoped styles (position: absolute) are applied when
modalRoot is provided but only asserts the backdrop exists; either update the
assertion to verify the style (get the element selected by
container.querySelector('[data-clerk-element="modalBackdrop"]') and assert its
computed style position is 'absolute') or change the test description/comment to
reflect that it only checks presence of the backdrop; reference APIKeyModal,
modalRoot, and the queried modalElement when making the change.

In @packages/ui/src/lazyModules/providers.tsx:
- Line 119: The type for getContainer is currently required but some usages
(e.g., blankCaptchaModal in Components.tsx and other LazyModalRenderer call
sites) omit it; update the declaration of getContainer in providers (the getter
on the modal/provider interface) to be optional by changing it to getContainer?:
() => HTMLElement | null so callers that don’t supply a custom container compile
cleanly and existing callers that pass getContainer continue to work.
🧹 Nitpick comments (4)
packages/ui/src/elements/Popover.tsx (1)

39-40: Consider memoizing the effectiveRoot computation.

While the current computation is inexpensive, wrapping it in useMemo would align with React best practices for derived values and ensure consistency if the portal root resolution logic becomes more complex in the future.

♻️ Proposed refactor
  const portalRoot = usePortalRoot();
- const effectiveRoot = root ?? portalRoot?.() ?? undefined;
+ const effectiveRoot = React.useMemo(() => root ?? portalRoot?.() ?? undefined, [root, portalRoot]);
packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)

2-4: Consider removing explicit React import if using modern JSX transform.

With the modern JSX transform (React 17+), explicit import React from 'react' is not required for JSX. Other tests in this file don't appear to need this import for JSX usage. If this import is only for JSX, it can be removed.

However, if the test framework or configuration requires it, this is fine to keep.

packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)

4-6: Consider removing explicit React import if using modern JSX transform.

Same consideration as in the UserButton test file - the explicit React import may not be necessary with the modern JSX transform.

packages/shared/src/react/PortalProvider.tsx (1)

56-65: Hook implementation is correct with good defensive checks.

The usePortalRoot hook properly handles both cases: returning the context-provided getContainer when inside a provider, and falling back to a null-returning function otherwise. The explicit return type and defensive conditional checks ensure type safety and runtime reliability.

💡 Optional: Simplify condition check

The condition on lines 59-61 could be slightly simplified while maintaining safety:

-  if (contextValue && 'getContainer' in contextValue && contextValue.getContainer) {
+  if (contextValue?.getContainer) {
     return contextValue.getContainer;
   }

However, the current explicit approach is more defensive and equally valid.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 694e7f9 and fa6ef65.

📒 Files selected for processing (34)
  • .changeset/wet-phones-camp.md
  • packages/astro/src/react/index.ts
  • packages/expo/src/index.ts
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/nextjs/src/index.ts
  • packages/nuxt/src/runtime/components/index.ts
  • packages/react-router/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
  • packages/react/src/contexts/index.ts
  • packages/shared/src/react/PortalProvider.tsx
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/shared/src/react/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/tanstack-react-start/src/client/index.ts
  • packages/ui/src/Components.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/ui/src/elements/Drawer.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/elements/Tooltip.tsx
  • packages/ui/src/lazyModules/providers.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/vue/src/components/PortalProvider.ts
  • packages/vue/src/components/index.ts
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/composables/index.ts
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/vue/src/keys.ts
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/index.ts

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

Avoid barrel files (index.ts re-exports) as they can cause circular dependencies

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/nuxt/src/runtime/components/index.ts
  • packages/nextjs/src/index.ts
  • packages/expo/src/index.ts
  • packages/vue/src/composables/index.ts
  • packages/shared/src/react/index.ts
  • packages/astro/src/react/index.ts
  • packages/tanstack-react-start/src/client/index.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Use ESLint with custom configurations tailored for different package types

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.{js,ts,jsx,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Use Prettier for code formatting across all packages

Files:

  • packages/react-router/src/client/index.ts
  • packages/vue/src/components/index.ts
  • packages/react/src/contexts/index.ts
  • packages/ui/src/elements/Popover.tsx
  • packages/nextjs/src/client-boundary/controlComponents.ts
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/nuxt/src/runtime/components/index.ts
  • packages/ui/src/elements/Tooltip.tsx
  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/vue/src/keys.ts
  • packages/nextjs/src/index.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/vue/src/components/ClerkHostRenderer.ts
  • packages/ui/src/elements/Drawer.tsx
  • packages/vue/src/composables/usePortalRoot.ts
  • packages/expo/src/index.ts
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/vue/src/composables/index.ts
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/shared/src/react/index.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/astro/src/react/index.ts
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/vue/src/components/PortalProvider.ts
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/tanstack-react-start/src/client/index.ts
  • packages/react/src/components/withClerk.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.tsx: Use error boundaries in React components
Minimize re-renders in React components

**/*.tsx: Use proper type definitions for props and state in React components
Leverage TypeScript's type inference where possible in React components
Use proper event types for handlers in React components
Implement proper generic types for reusable React components
Use proper type guards for conditional rendering in React components

Files:

  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/ui/src/elements/Tooltip.tsx
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/elements/Drawer.tsx
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/react/src/components/withClerk.tsx
**/*.{md,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Update documentation for API changes

Files:

  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/ui/src/elements/Tooltip.tsx
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/elements/Drawer.tsx
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/react/src/components/withClerk.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react.mdc)

**/*.{jsx,tsx}: Always use functional components with hooks instead of class components
Follow PascalCase naming for components (e.g., UserProfile, NavigationMenu)
Keep components focused on a single responsibility - split large components
Limit component size to 150-200 lines; extract logic into custom hooks
Use composition over inheritance - prefer smaller, composable components
Export components as named exports for better tree-shaking
One component per file with matching filename and component name
Separate UI components from business logic components
Use useState for simple state management in React components
Use useReducer for complex state logic in React components
Implement proper state initialization in React components
Use proper state updates with callbacks in React components
Implement proper state cleanup in React components
Use Context API for theme/authentication state management
Implement proper state persistence in React applications
Use React.memo for expensive components
Implement proper useCallback for handlers in React components
Use proper useMemo for expensive computations in React components
Implement proper virtualization for lists in React components
Use proper code splitting with React.lazy in React applications
Implement proper cleanup in useEffect hooks
Use proper refs for DOM access in React components
Implement proper event listener cleanup in React components
Use proper abort controllers for fetch in React components
Implement proper subscription cleanup in React components
Use proper HTML elements for semantic HTML in React components
Implement proper ARIA attributes for accessibility in React components
Use proper heading hierarchy in React components
Implement proper form labels in React components
Use proper button types in React components
Implement proper focus management for keyboard navigation in React components
Use proper keyboard shortcuts in React components
Implement proper tab order in React components
Use proper ...

Files:

  • packages/ui/src/elements/Popover.tsx
  • packages/ui/src/components/PricingTable/PricingTable.tsx
  • packages/ui/src/elements/Menu.tsx
  • packages/ui/src/elements/Modal.tsx
  • packages/ui/src/elements/Tooltip.tsx
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/elements/Drawer.tsx
  • packages/ui/src/lazyModules/providers.tsx
  • packages/ui/src/Components.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/shared/src/react/PortalProvider.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
  • packages/ui/src/components/UserButton/useMultisessionActions.tsx
  • packages/react/src/components/withClerk.tsx
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features

Files:

  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use real Clerk instances for integration tests

Files:

  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Use React Testing Library for component testing

Files:

  • packages/vue/src/composables/__tests__/usePortalRoot.test.ts
  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.test.tsx

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use React Testing Library for component testing

Files:

  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
**/*.{test,spec}.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react.mdc)

**/*.{test,spec}.{jsx,tsx}: Use React Testing Library for unit testing React components
Test component behavior, not implementation details
Use proper test queries in React Testing Library tests
Implement proper test isolation in React component tests
Use proper test coverage in React component tests
Test component interactions in integration tests
Use proper test data in React component tests
Implement proper test setup in React component tests
Use proper test cleanup in React component tests
Implement proper test assertions in React component tests
Use proper test structure for React component tests

Files:

  • packages/shared/src/react/__tests__/PortalProvider.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx
  • packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx
🧬 Code graph analysis (10)
packages/shared/src/react/__tests__/PortalProvider.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (2)
  • usePortalRoot (56-65)
  • UNSAFE_PortalProvider (45-49)
packages/vue/src/composables/usePortalRoot.ts (2)
packages/vue/src/composables/index.ts (1)
  • usePortalRoot (17-17)
packages/vue/src/keys.ts (1)
  • PortalInjectionKey (23-25)
packages/shared/src/types/clerk.ts (1)
packages/ui/src/types.ts (7)
  • SignInProps (37-37)
  • __internal_UserVerificationProps (31-31)
  • SignUpProps (38-38)
  • UserProfileProps (41-41)
  • OrganizationProfileProps (35-35)
  • CreateOrganizationProps (32-32)
  • WaitlistProps (42-42)
packages/ui/src/lazyModules/providers.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
  • UNSAFE_PortalProvider (45-49)
packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
  • UNSAFE_PortalProvider (45-49)
packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
  • UNSAFE_PortalProvider (45-49)
packages/shared/src/react/PortalProvider.tsx (2)
packages/shared/src/react/hooks/index.ts (1)
  • createContextAndHook (1-1)
packages/shared/src/react/index.ts (2)
  • UNSAFE_PortalProvider (24-24)
  • usePortalRoot (24-24)
packages/vue/src/components/PortalProvider.ts (3)
packages/nuxt/src/runtime/components/index.ts (1)
  • UNSAFE_PortalProvider (28-28)
packages/vue/src/components/index.ts (1)
  • UNSAFE_PortalProvider (33-33)
packages/vue/src/keys.ts (1)
  • PortalInjectionKey (23-25)
packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx (2)
packages/ui/src/components/APIKeys/APIKeyModal.tsx (1)
  • APIKeyModal (36-44)
packages/shared/src/react/PortalProvider.tsx (1)
  • UNSAFE_PortalProvider (45-49)
packages/react/src/components/withClerk.tsx (1)
packages/shared/src/react/PortalProvider.tsx (1)
  • usePortalRoot (56-65)
🔇 Additional comments (59)
packages/tanstack-react-start/src/client/index.ts (1)

3-3: LGTM! Consistent API expansion.

The re-export of UNSAFE_PortalProvider follows the established pattern across framework integrations. The UNSAFE_ prefix appropriately signals that this is an experimental API.

packages/ui/src/elements/Menu.tsx (1)

202-202: LGTM! Good UX improvement.

Adding whiteSpace: 'nowrap' prevents menu item text from wrapping, which maintains consistent and predictable menu item presentation.

packages/nextjs/src/client-boundary/controlComponents.ts (1)

18-18: LGTM! Consistent with Next.js client boundary pattern.

The addition of UNSAFE_PortalProvider to the control components re-exports is consistent with the existing pattern and appropriately marked with the 'use client' directive for Next.js client boundaries.

packages/react-router/src/client/index.ts (1)

4-4: LGTM! Consistent framework integration.

The re-export of UNSAFE_PortalProvider aligns with the coordinated API expansion across framework integrations and follows the established export pattern.

packages/react/src/contexts/index.ts (1)

2-2: LGTM! Clean re-export of the new Portal API.

The re-export follows established patterns and the UNSAFE_ prefix appropriately signals the experimental nature of this API.

packages/shared/src/react/index.ts (1)

24-24: LGTM! Proper public API exposure.

Both the provider component and the hook are correctly exported together, enabling consumers to use the Portal customization feature.

packages/ui/src/components/UserButton/useMultisessionActions.tsx (4)

3-3: LGTM! Proper import of the portal hook.


30-30: LGTM! Correct hook usage.

The hook is called at the top level of the custom hook, following React's Rules of Hooks.


50-50: LGTM! Consistent container propagation.

The getContainer is properly passed to openUserProfile, enabling portal customization for the user profile modal.


63-67: LGTM! Consistent pattern across call sites.

The second openUserProfile call also receives getContainer, maintaining consistency with Line 50.

packages/ui/src/lazyModules/providers.tsx (3)

3-3: LGTM! Proper import of the portal provider.


89-100: LGTM! Clean portal provider integration.

The UNSAFE_PortalProvider correctly wraps the Portal component and properly extracts getContainer from component props using optional chaining.


133-155: LGTM! Consistent portal provider usage in modal renderer.

The UNSAFE_PortalProvider correctly wraps the Modal component, and the usage of props.getContainer is consistent with the type definition at Line 119.

packages/react/src/components/withClerk.tsx (3)

1-1: LGTM! Proper import of the portal hook.


23-23: LGTM! Correct hook usage.

The hook is called at the top level, following React's Rules of Hooks.


30-32: Type assertion handles getContainer injection without requiring component type updates.

The HOC currently injects getContainer using a type assertion (as P), which allows the prop to be passed to the wrapped component even though it's not declared in the component's prop type. This works because no wrapped components actually use getContainer – it's only injected but never consumed. The current implementation is functionally correct but could be improved for type safety by either:

  1. Adding getContainer?: () => HTMLElement | null to the WithClerkProp type definition so all wrapped components properly declare it, or
  2. Explicitly documenting this pattern if getContainer is intentionally unused or reserved for future use.
packages/shared/src/types/clerk.ts (1)

1425-1432: LGTM! Well-documented and consistent type additions.

The getContainer property is correctly defined across all modal prop types with consistent JSDoc documentation and appropriate type signature. The optional nature ensures backward compatibility, and the examples in JSDoc (Radix Dialog, React Aria) provide helpful context for consumers.

Also applies to: 1434-1440, 1582-1589, 1631-1638, 1681-1688, 1714-1721, 1944-1951

packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx (2)

1-1: LGTM! Correct hook usage.

The usePortalRoot hook is properly imported and called at the component's top level, following React hook rules.

Also applies to: 28-28


80-80: LGTM! Consistent container propagation.

The getContainer is correctly passed to both openCreateOrganization and openOrganizationProfile functions, enabling modals to render into custom portal containers when provided via UNSAFE_PortalProvider.

Also applies to: 92-92

packages/vue/src/components/index.ts (1)

33-33: LGTM! Consistent API surface expansion.

The UNSAFE_PortalProvider re-export aligns with the cross-framework portal provider pattern. The UNSAFE_ prefix appropriately signals the experimental nature of this API.

packages/astro/src/react/index.ts (1)

11-11: LGTM! Consistent framework integration.

The UNSAFE_PortalProvider re-export from @clerk/shared/react maintains consistency across framework integrations (React, Vue, Astro, etc.). The UNSAFE_ prefix appropriately signals the experimental nature of this API.

packages/ui/src/components/PricingTable/PricingTable.tsx (2)

1-1: LGTM! Correct hook usage.

The usePortalRoot hook is properly imported and called at the component's top level within PricingTableRoot, adhering to React hook rules.

Also applies to: 13-13


53-70: LGTM! Appropriate container usage.

The getContainer is correctly passed to clerk.openSignIn() only in the modal flow (line 56), while the redirect flow (line 58) appropriately omits it since redirects don't require portal containers. This demonstrates proper understanding of when portal containers are needed.

packages/nuxt/src/runtime/components/index.ts (1)

28-28: LGTM! Export follows established pattern.

The re-export of UNSAFE_PortalProvider is consistent with other exports and correctly signals the experimental nature of the API with the UNSAFE_ prefix.

packages/vue/src/composables/index.ts (1)

16-17: LGTM! Export follows established pattern.

The addition of usePortalRoot export is consistent with the existing export pattern in this file and properly expands the composables API surface.

packages/expo/src/index.ts (1)

20-20: LGTM! Export follows established pattern.

The re-export of UNSAFE_PortalProvider from @clerk/react is appropriately placed and consistent with similar exports across other packages in this PR.

packages/nextjs/src/index.ts (1)

11-11: LGTM! Export follows required Next.js pattern.

The addition of UNSAFE_PortalProvider to the explicit named exports correctly follows the pattern required for Next.js App Router compatibility, as indicated by the warning comment above.

packages/ui/src/elements/Popover.tsx (1)

1-1: LGTM! Portal root integration implemented correctly.

The integration of usePortalRoot enables custom portal container resolution while maintaining backward compatibility through the fallback chain (explicit root prop → portal provider context → undefined). The optional chaining correctly handles cases where no portal provider is present.

Also applies to: 39-40, 45-45

packages/ui/src/elements/Drawer.tsx (3)

1-1: LGTM: Portal root resolution is correctly implemented.

The import and fallback chain properly handle the precedence: explicit portalProps.root → context portal root → undefined (default). The optional chaining on portalRoot?.() correctly handles cases outside the provider.

Also applies to: 91-92


115-115: LGTM: Context propagation is correct.

Spreading portalProps while explicitly overriding root with effectivePortalRoot ensures the computed value propagates correctly to drawer context consumers.


122-127: The current implementation is correct. Drawer.Root wraps business components (e.g., Checkout, SubscriptionDetails) that internally use Drawer.Content, Drawer.Header, and other subcomponents. These business components are not trigger elements—they are the drawer's content. Portaling all children together is the intended and appropriate pattern for drawer/modal implementations, unlike tooltip/popover patterns that only portal a single floating element. The design is sound and no changes are needed.

Likely an incorrect or invalid review comment.

packages/ui/src/elements/Tooltip.tsx (1)

19-19: LGTM: Tooltip portal integration is correctly implemented.

The portal root resolution is properly integrated into the Content component. The pattern is simpler than Drawer since Tooltip doesn't accept explicit portal props, making portalRoot?.() ?? undefined the appropriate fallback chain.

Also applies to: 197-198, 205-205

packages/shared/src/react/__tests__/PortalProvider.test.tsx (4)

7-24: LGTM: Test correctly verifies context propagation.

The test properly validates that usePortalRoot returns the same function reference provided to UNSAFE_PortalProvider, using identity comparison to ensure the exact function is passed through context.


26-51: LGTM: Test correctly verifies provider scope boundaries.

The test properly validates that only components within the UNSAFE_PortalProvider receive the custom container, while components outside receive null, confirming the provider's scope is correctly limited to its descendants.


54-71: LGTM: Test correctly verifies function invocation.

The test properly validates that invoking the returned function (portalRoot()) yields the expected container element, complementing the identity test with behavioral verification.


73-82: LGTM: Tests cover critical edge cases.

Both tests properly validate important edge cases:

  1. Default behavior outside any provider (returns null)
  2. Nested provider precedence (innermost provider wins)

These tests ensure the context system behaves correctly in complex component trees.

Also applies to: 84-102

packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx (3)

10-32: LGTM: Test correctly verifies modal renders in custom container.

The test properly validates that the modal renders inside the provided modalRoot container. The manual ref assignment (modalRoot.current = container) is acceptable for test purposes and allows direct control over the ref value.

Cleanup is properly handled with document.body.removeChild(container).


58-87: LGTM: Test correctly verifies precedence behavior.

The test properly validates that an explicit modalRoot prop takes precedence over the UNSAFE_PortalProvider context, which is the correct and expected behavior. Assertions and cleanup are both correct.


5-5: Incorrect import path: use @clerk/shared/react instead of @clerk/react.

This test file is within packages/ui, and the production code in this same package (packages/ui/src/lazyModules/providers.tsx) imports UNSAFE_PortalProvider from @clerk/shared/react. The test file should follow the same import pattern for consistency within the package.

Change line 5 from:

import { UNSAFE_PortalProvider } from '@clerk/react';

to:

import { UNSAFE_PortalProvider } from '@clerk/shared/react';

Likely an incorrect or invalid review comment.

packages/vue/src/composables/usePortalRoot.ts (1)

1-19: LGTM: Vue composable correctly mirrors React implementation.

The composable properly uses Vue's inject to access the portal context and returns either the provided getContainer function or a null-returning fallback. The implementation is consistent with the React version and follows Vue patterns.

The defensive check on line 13 for context.getContainer is acceptable, though TypeScript should guarantee its presence given the InjectionKey type definition.

packages/ui/src/components/UserButton/__tests__/UserButton.test.tsx (1)

90-115: LGTM! Test correctly verifies PortalProvider integration.

The test properly validates that when UserButton is wrapped in UNSAFE_PortalProvider, the getContainer function is passed through to openUserProfile. The test setup follows the existing patterns in the file.

packages/ui/src/elements/Modal.tsx (3)

1-1: LGTM!

Import is correctly added for the new usePortalRoot hook.


30-30: LGTM!

Correctly retrieves the portal container getter from context using the new hook.


56-64: LGTM! Clean fallback chain for portal root resolution.

The priority order is correct: explicit portalRoot prop takes precedence, then context-provided container, then undefined (which defaults to document.body). The optional chaining on portalRootFromContext?.() correctly handles the case when no provider is present.

packages/vue/src/keys.ts (1)

22-25: LGTM!

The new PortalInjectionKey follows the established pattern used by other injection keys in this file (ClerkInjectionKey, UserButtonInjectionKey, etc.). The type signature correctly matches the provider and consumer implementations.

packages/vue/src/composables/__tests__/usePortalRoot.test.ts (4)

1-7: LGTM!

Imports are correct and follow the project's testing conventions with @testing-library/vue and vitest.


8-36: LGTM! Good test coverage for basic provider behavior.

The first two tests properly verify:

  1. usePortalRoot returns the provided container when inside the provider
  2. usePortalRoot returns null when outside the provider

These are the fundamental behaviors of the composable.


38-71: LGTM! Important test for provider scoping.

This test correctly validates that the provider only affects descendant components, which is a key requirement for the UNSAFE_PortalProvider feature. Components outside the provider should get the null fallback.


73-99: LGTM! Nested provider test validates correct precedence.

This test ensures that when providers are nested, the innermost provider's getContainer takes precedence. This is the expected behavior for Vue's provide/inject and matches the React implementation.

packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)

545-589: LGTM! Comprehensive PortalProvider tests for OrganizationSwitcher.

Both tests properly verify that getContainer is passed through to the respective open handlers (openOrganizationProfile and openCreateOrganization). The use of waitFor for menu item queries is appropriate given the async nature of menu rendering.

packages/vue/src/components/PortalProvider.ts (2)

5-34: LGTM! Excellent JSDoc documentation.

The documentation clearly explains:

  • The purpose of the component
  • The scope of affected components
  • A practical example with Reka UI Dialog

This follows the coding guidelines for comprehensive JSDoc comments on public APIs.


35-52: LGTM with minor consideration for reactivity.

The implementation is clean and follows Vue patterns correctly. One consideration: the provide call passes props.getContainer directly. If the parent component changes the getContainer prop, consumers using inject won't automatically receive the updated value since provide is not reactive by default.

However, since getContainer is a function that typically returns a ref's value (e.g., () => dialogContentRef?.$el), the actual container resolution happens at call-time, so this should work correctly in practice. The React equivalent also uses a similar pattern.

packages/vue/src/components/ClerkHostRenderer.ts (4)

4-4: LGTM!

Import correctly added for the new composable.


48-48: LGTM!

Correctly retrieves the portal container getter from context using the composable at setup time.


57-67: LGTM! Clean integration of getContainer into mount/open flows.

The propsWithContainer pattern cleanly merges the context-provided getContainer into the props passed to mount() and open(), enabling portal container customization for Clerk components.


71-83: LGTM! Consistent pattern for updateProps.

The same propsWithContainer pattern is correctly applied in the props watcher, ensuring that prop updates also include the getContainer function.

packages/ui/src/Components.tsx (1)

460-588: LGTM! Consistent portal container forwarding.

The getContainer prop is correctly forwarded from each modal's state to the corresponding LazyModalRenderer across all interactive modals (SignIn, SignUp, UserProfile, UserVerification, OrganizationProfile, CreateOrganization, Waitlist). The optional chaining handles null state appropriately, and the exclusion of blankCaptchaModal is intentional given its special non-interactive styling.

packages/shared/src/react/PortalProvider.tsx (2)

7-14: Well-defined type with clear documentation.

The PortalProviderProps type is properly structured with a clear JSDoc comment explaining the purpose of the getContainer function. The type signature correctly represents a function returning HTMLElement | null.


45-49: LGTM! Proper context provider implementation.

The component correctly uses useMemo to memoize the context value, preventing unnecessary re-renders of consumers. The memoization on getContainer follows React best practices, and the implementation is clean and straightforward.

Comment on lines +1 to +3
---
'@clerk/shared': patch
---
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

semantic versioning UNSAFE_ prefix new exports library npm package

💡 Result:

Short answer: use the UNSAFE_ prefix as a convention (like React) to mark an export as "unsafe/unstable/deprecated", but follow semver rules: adding a new UNSAFE_ export is a backward‑compatible new feature (bump minor); renaming/removing/changing existing exports (including replacing an old name with an UNSAFE_ name) is breaking (bump major). Also be careful when changing package "exports" because introducing or restricting exports itself can be breaking. [1][2][3]

Recommendations

  • To mark a new unsafe API: export it under the UNSAFE_ name (example: export const UNSAFE_doThing = …) and publish a minor release. This gives users access without breaking callers that import the stable API. [1][3]
  • To deprecate an existing export: do NOT rename or remove it in a minor/patch release. Renaming (e.g. foo → UNSAFE_foo) is a breaking change and requires a major version or a documented migration path (or keep the old name as an alias for one major cycle). [1][2]
  • If you add an "exports" field or tighten existing exports, ensure you include all previous entry points or treat it as a breaking change. Node treats introducing/restricting "exports" as potentially breaking. [2]
  • Document clearly (README/changelog/TS types) that UNSAFE_ exports are unstable and may be removed in a future major release.

Sources

  • React blog (UNSAFE_ prefix rationale and usage). [1]
  • Node.js docs on "exports" and breaking changes when introducing/restricting exports. [2]
  • npm / semver guidance on version increments (patch/minor/major). [3]

🏁 Script executed:

# Find the actual changes in this changeset to see what was added
git log --oneline --all | head -20

Repository: clerk/javascript

Length of output: 225


🏁 Script executed:

# Look for UNSAFE_PortalProvider in the codebase
rg "UNSAFE_PortalProvider" -t ts -t tsx -t js -t jsx

Repository: clerk/javascript

Length of output: 87


🏁 Script executed:

# Fix the rg command - remove unrecognized type
rg "UNSAFE_PortalProvider"

Repository: clerk/javascript

Length of output: 6459


🏁 Script executed:

# Check the commit that added UNSAFE_PortalProvider
git log --oneline | grep -i "unsafe_portal"

Repository: clerk/javascript

Length of output: 124


🏁 Script executed:

# Get the detailed changes for this feature
git show fa6ef65a --stat

Repository: clerk/javascript

Length of output: 50373


🏁 Script executed:

# Check what files are in the @clerk/shared package
fd "package.json" | head -20

Repository: clerk/javascript

Length of output: 947


🏁 Script executed:

cat .changeset/wet-phones-camp.md

Repository: clerk/javascript

Length of output: 90


🏁 Script executed:

# Find related changesets for context on versioning convention
find .changeset -name "*.md" -type f -exec grep -l "UNSAFE_" {} \;

Repository: clerk/javascript

Length of output: 42


Mark this changeset as minor instead of patch.

Adding a new public export (UNSAFE_PortalProvider) is a backward-compatible feature that should trigger a minor version bump per semantic versioning standards. The UNSAFE_ prefix is a convention to indicate instability but does not change the fact that this is a new feature being added to the public API.

🤖 Prompt for AI Agents
In @.changeset/wet-phones-camp.md around lines 1 - 3, The changeset currently
declares a patch release for '@clerk/shared' but you added a new public export
(UNSAFE_PortalProvider); update the changeset header from 'patch' to 'minor' so
the release semver reflects a new public API export; edit the top YAML block in
.changeset/wet-phones-camp.md and replace 'patch' with 'minor' for the
'@clerk/shared' entry.

Comment on lines +34 to +56
it('applies scoped portal container styles when modalRoot provided', () => {
const modalRoot = React.createRef<HTMLDivElement>();
const container = document.createElement('div');
modalRoot.current = container;
document.body.appendChild(container);

const { container: testContainer } = render(
<APIKeyModal
modalRoot={modalRoot}
handleOpen={() => {}}
handleClose={() => {}}
canCloseModal={true}
>
<div>Test</div>
</APIKeyModal>,
);

// The modal should have scoped styles (position: absolute) when modalRoot is provided
const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]');
expect(modalElement).toBeTruthy();

document.body.removeChild(container);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test assertion is weaker than its description suggests.

The test description states it "applies scoped portal container styles when modalRoot provided" and the comment mentions checking for position: absolute, but the assertion only verifies that the backdrop element exists, not that the styles are actually applied.

🔍 Suggested improvement
     // The modal should have scoped styles (position: absolute) when modalRoot is provided
     const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]');
-    expect(modalElement).toBeTruthy();
+    expect(modalElement).toBeTruthy();
+    const computedStyle = window.getComputedStyle(modalElement);
+    expect(computedStyle.position).toBe('absolute');

Alternatively, if style verification is intentionally omitted, update the test description and comment to match the actual assertion.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('applies scoped portal container styles when modalRoot provided', () => {
const modalRoot = React.createRef<HTMLDivElement>();
const container = document.createElement('div');
modalRoot.current = container;
document.body.appendChild(container);
const { container: testContainer } = render(
<APIKeyModal
modalRoot={modalRoot}
handleOpen={() => {}}
handleClose={() => {}}
canCloseModal={true}
>
<div>Test</div>
</APIKeyModal>,
);
// The modal should have scoped styles (position: absolute) when modalRoot is provided
const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]');
expect(modalElement).toBeTruthy();
document.body.removeChild(container);
});
it('applies scoped portal container styles when modalRoot provided', () => {
const modalRoot = React.createRef<HTMLDivElement>();
const container = document.createElement('div');
modalRoot.current = container;
document.body.appendChild(container);
const { container: testContainer } = render(
<APIKeyModal
modalRoot={modalRoot}
handleOpen={() => {}}
handleClose={() => {}}
canCloseModal={true}
>
<div>Test</div>
</APIKeyModal>,
);
// The modal should have scoped styles (position: absolute) when modalRoot is provided
const modalElement = container.querySelector('[data-clerk-element="modalBackdrop"]');
expect(modalElement).toBeTruthy();
const computedStyle = window.getComputedStyle(modalElement);
expect(computedStyle.position).toBe('absolute');
document.body.removeChild(container);
});
🤖 Prompt for AI Agents
In @packages/ui/src/components/APIKeys/__tests__/APIKeyModal.test.tsx around
lines 34 - 56, The test description for APIKeyModal in APIKeyModal.test.tsx
claims scoped styles (position: absolute) are applied when modalRoot is provided
but only asserts the backdrop exists; either update the assertion to verify the
style (get the element selected by
container.querySelector('[data-clerk-element="modalBackdrop"]') and assert its
computed style position is 'absolute') or change the test description/comment to
reflect that it only checks presence of the backdrop; reference APIKeyModal,
modalRoot, and the queried modalElement when making the change.

canCloseModal?: boolean;
modalId?: string;
modalStyle?: React.CSSProperties;
getContainer: () => HTMLElement | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the specific file and line mentioned
head -150 packages/ui/src/lazyModules/providers.tsx | tail -50

Repository: clerk/javascript

Length of output: 1739


🏁 Script executed:

# Run the suggested script to find all LazyModalRenderer usages
rg -n -A 10 -B 3 'LazyModalRenderer' --type ts --type tsx -g '!*.test.*'

Repository: clerk/javascript

Length of output: 87


🏁 Script executed:

# Search for LazyModalRenderer usage - fixed file type flags
rg -n -A 10 -B 3 'LazyModalRenderer' --type ts --type typescript -g '!*.test.*' -g '!*.spec.*'

Repository: clerk/javascript

Length of output: 94


🏁 Script executed:

# Alternative: Search without type restrictions to find all usages
rg -n -A 5 '<LazyModalRenderer' -g '!*.test.*' -g '!*.spec.*'

Repository: clerk/javascript

Length of output: 3820


🏁 Script executed:

# Get more context to see the full prop list including getContainer
rg -n -A 20 '<LazyModalRenderer' -g '!*.test.*' -g '!*.spec.*' packages/ui/src/Components.tsx

Repository: clerk/javascript

Length of output: 7035


Make getContainer optional to match actual usage patterns.

The type definition requires getContainer (no ? modifier), but the blankCaptchaModal instance at line 594 in packages/ui/src/Components.tsx does not provide this prop. All other seven LazyModalRenderer call sites pass it via getContainer={<modalName>?.getContainer}. Change the type definition to getContainer?: () => HTMLElement | null; to align with the actual usage where some modals may not require a custom container.

🤖 Prompt for AI Agents
In @packages/ui/src/lazyModules/providers.tsx at line 119, The type for
getContainer is currently required but some usages (e.g., blankCaptchaModal in
Components.tsx and other LazyModalRenderer call sites) omit it; update the
declaration of getContainer in providers (the getter on the modal/provider
interface) to be optional by changing it to getContainer?: () => HTMLElement |
null so callers that don’t supply a custom container compile cleanly and
existing callers that pass getContainer continue to work.

Sign up for free to 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.

4 participants