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 interact-able.

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 UNSAFE_PortalProvider and usePortalRoot across frameworks; components can accept a getContainer to target custom portal containers.
  • UI

    • Modals, popovers, tooltips, drawers, menus and lazy modal rendering now resolve and honor the specified portal container for consistent rendering.
  • Style

    • Prevented menu item text wrapping for improved layout.
  • Tests

    • Added extensive tests for portal provider/composable behavior and modal/portal rendering.
  • Chores

    • Updated bundle size thresholds.

✏️ 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: aee4553

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

This PR includes changesets to release 20 packages
Name Type
@clerk/tanstack-react-start Minor
@clerk/react-router Minor
@clerk/nextjs Minor
@clerk/shared Minor
@clerk/astro Minor
@clerk/react Minor
@clerk/expo Minor
@clerk/nuxt Minor
@clerk/vue Minor
@clerk/ui Minor
@clerk/agent-toolkit Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/testing 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.

Project Deployment Review Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jan 20, 2026 4:38pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

📝 Walkthrough

Walkthrough

Introduces a portal provider system: adds UNSAFE_PortalProvider and usePortalRoot in shared React and Vue layers, plus a Vue PortalInjectionKey and composable. Components that render portals (modals, popovers, tooltips, drawers, lazy renderers, host renderers, organization/user flows) now obtain or accept a getContainer function. Multiple packages re-export the provider/hook, several modal prop types gained an optional getContainer, tests for React and Vue portal behavior were added, and UI components were updated to use the provider. Bundlewatch thresholds were adjusted.

🚥 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 PR title accurately describes the main feature being introduced: a new component called UNSAFE_PortalProvider that allows custom portal containers for Clerk UI elements.

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


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/[email protected] --save-exact

@clerk/astro

npm i @clerk/[email protected] --save-exact

@clerk/backend

npm i @clerk/[email protected] --save-exact

@clerk/chrome-extension

npm i @clerk/[email protected] --save-exact

@clerk/clerk-js

npm i @clerk/[email protected] --save-exact

@clerk/elements

npm i @clerk/[email protected] --save-exact

@clerk/clerk-expo

npm i @clerk/[email protected] --save-exact

@clerk/expo-passkeys

npm i @clerk/[email protected] --save-exact

@clerk/express

npm i @clerk/[email protected] --save-exact

@clerk/fastify

npm i @clerk/[email protected] --save-exact

@clerk/localizations

npm i @clerk/[email protected] --save-exact

@clerk/nextjs

npm i @clerk/[email protected] --save-exact

@clerk/nuxt

npm i @clerk/[email protected] --save-exact

@clerk/clerk-react

npm i @clerk/[email protected] --save-exact

@clerk/react-router

npm i @clerk/[email protected] --save-exact

@clerk/remix

npm i @clerk/[email protected] --save-exact

@clerk/shared

npm i @clerk/[email protected] --save-exact

@clerk/tanstack-react-start

npm i @clerk/[email protected] --save-exact

@clerk/testing

npm i @clerk/[email protected] --save-exact

@clerk/themes

npm i @clerk/[email protected] --save-exact

@clerk/types

npm i @clerk/[email protected] --save-exact

@clerk/vue

npm i @clerk/[email protected] --save-exact

@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.v20260109194807
@clerk/astro 3.0.0-snapshot.v20260109194807
@clerk/backend 3.0.0-snapshot.v20260109194807
@clerk/chrome-extension 3.0.0-snapshot.v20260109194807
@clerk/clerk-js 6.0.0-snapshot.v20260109194807
@clerk/dev-cli 1.0.0-snapshot.v20260109194807
@clerk/expo 3.0.0-snapshot.v20260109194807
@clerk/expo-passkeys 1.0.0-snapshot.v20260109194807
@clerk/express 2.0.0-snapshot.v20260109194807
@clerk/fastify 2.6.9-snapshot.v20260109194807
@clerk/localizations 4.0.0-snapshot.v20260109194807
@clerk/msw 0.0.1-snapshot.v20260109194807
@clerk/nextjs 7.0.0-snapshot.v20260109194807
@clerk/nuxt 2.0.0-snapshot.v20260109194807
@clerk/react 6.0.0-snapshot.v20260109194807
@clerk/react-router 3.0.0-snapshot.v20260109194807
@clerk/shared 4.0.0-snapshot.v20260109194807
@clerk/tanstack-react-start 1.0.0-snapshot.v20260109194807
@clerk/testing 2.0.0-snapshot.v20260109194807
@clerk/ui 1.0.0-snapshot.v20260109194807
@clerk/upgrade 2.0.0-snapshot.v20260109194807
@clerk/vue 2.0.0-snapshot.v20260109194807

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

npm i @clerk/[email protected] --save-exact

@clerk/astro

npm i @clerk/[email protected] --save-exact

@clerk/backend

npm i @clerk/[email protected] --save-exact

@clerk/chrome-extension

npm i @clerk/[email protected] --save-exact

@clerk/clerk-js

npm i @clerk/[email protected] --save-exact

@clerk/dev-cli

npm i @clerk/[email protected] --save-exact

@clerk/expo

npm i @clerk/[email protected] --save-exact

@clerk/expo-passkeys

npm i @clerk/[email protected] --save-exact

@clerk/express

npm i @clerk/[email protected] --save-exact

@clerk/fastify

npm i @clerk/[email protected] --save-exact

@clerk/localizations

npm i @clerk/[email protected] --save-exact

@clerk/msw

npm i @clerk/[email protected] --save-exact

@clerk/nextjs

npm i @clerk/[email protected] --save-exact

@clerk/nuxt

npm i @clerk/[email protected] --save-exact

@clerk/react

npm i @clerk/[email protected] --save-exact

@clerk/react-router

npm i @clerk/[email protected] --save-exact

@clerk/shared

npm i @clerk/[email protected] --save-exact

@clerk/tanstack-react-start

npm i @clerk/[email protected] --save-exact

@clerk/testing

npm i @clerk/[email protected] --save-exact

@clerk/ui

npm i @clerk/[email protected] --save-exact

@clerk/upgrade

npm i @clerk/[email protected] --save-exact

@clerk/vue

npm i @clerk/[email protected] --save-exact

Copy link
Member

@wobsoriano wobsoriano left a comment

Choose a reason for hiding this comment

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

Looks good! Test it locally with the React and Vue SDKs:

  1. React using shadcn + Dialog component
React_Unsafe_PortalProvider.mov
  1. Vue using shadcn-vue + Dialog component
Vue_Unsafe_PortalProvider.mov

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: 2

🤖 Fix all issues with AI agents
In
`@packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx`:
- Around line 641-661: The test fails because the user fixture lacks
create_organization_enabled, so update the setup in the createFixtures callback
(the call to f.withUser) to include create_organization_enabled: true (e.g.,
f.withUser({ email_addresses: ['[email protected]'], create_organization_enabled:
true })) so the "Create organization" menu item is rendered; keep the rest of
the test that wraps OrganizationSwitcher with UNSAFE_PortalProvider
(getContainer) and asserts fixtures.clerk.openCreateOrganization was called with
expect.objectContaining({ getContainer }).
- Around line 619-639: The test fails because no organization_memberships are
set up, so the "Manage Organization" menu item never appears; update the fixture
setup inside the createFixtures callback (the function passed to createFixtures
and its fixture builder f) to include an organization membership for the user by
calling the fixture method that creates memberships (e.g.,
f.withOrganizationMemberships(...) or the project’s equivalent) after
f.withOrganizations() so the user is a member of the created organization and
the OrganizationSwitcher test can find and click "Manage Organization".

@alexcarpenter alexcarpenter merged commit 79e2622 into main Jan 20, 2026
38 checks passed
@alexcarpenter alexcarpenter deleted the alexcarpenter/portal-provider-3 branch January 20, 2026 16:56
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.

5 participants