Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix(react): Refactor to allow inlined Ui type
  • Loading branch information
dstaley committed Apr 2, 2026
commit b4057e40c3367ef21f9e896d21c83ce5caa88afb
3 changes: 1 addition & 2 deletions packages/react/src/contexts/ClerkProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ClerkContextProvider } from '@clerk/shared/react';
import type { Ui } from '@clerk/ui/internal';
import React from 'react';

import { multipleClerkProvidersError } from '../errors/messages';
import { IsomorphicClerk } from '../isomorphicClerk';
import type { ClerkProviderProps, IsomorphicClerkOptions } from '../types';
import type { ClerkProviderProps, IsomorphicClerkOptions, Ui } from '../types';
import { mergeWithEnv, withMaxAllowedInstancesGuard } from '../utils';
import { IS_REACT_SHARED_VARIANT_COMPATIBLE } from '../utils/versionCheck';

Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/contexts/__tests__/ClerkProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ import { dark } from '@clerk/ui/themes';
import { describe, expectTypeOf, it } from 'vitest';

import type { ClerkProvider } from '../ClerkProvider';
import type { ClerkProviderProps as GenericClerkProviderProps, Ui } from '../../types';

type ClerkProviderProps = Parameters<typeof ClerkProvider>[0];
type CustomAppearance = {
options?: {
customFlag?: boolean;
};
};
const customUi = {} as Ui<CustomAppearance>;

describe('ClerkProvider', () => {
describe('Type tests', () => {
Expand Down Expand Up @@ -85,6 +92,19 @@ describe('ClerkProvider', () => {
// appearance: { elements: { nonExistentKey: '' } },
// }).not.toMatchTypeOf<ClerkProviderProps>();
});

it('is driven by the passed ui object type', () => {
expectTypeOf({
...defaultProps,
ui: customUi,
appearance: { options: { customFlag: true } },
}).toMatchTypeOf<GenericClerkProviderProps<typeof customUi>>();

expectTypeOf({
...defaultProps,
appearance: { options: { customFlag: true } },
}).not.toMatchTypeOf<ClerkProviderProps>();
});
});

describe('localization', () => {
Expand Down
5 changes: 2 additions & 3 deletions packages/react/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { InternalClerkScriptProps } from '@clerk/shared/types';
import type { Ui } from '@clerk/ui/internal';
import type React from 'react';

import { ClerkProvider } from './contexts/ClerkProvider';
import type { ClerkProviderProps } from './types';
import type { ClerkProviderProps, Ui } from './types';

export { setErrorThrowerOptions } from './errors/errorThrower';
export { MultisessionAppSupport } from './components/controlComponents';
Expand All @@ -23,7 +22,7 @@ export {
setClerkJsLoadingErrorPackageName,
} from '@clerk/shared/loadClerkJsScript';

export type { Ui } from '@clerk/ui/internal';
export type { Ui } from './types';

export type { InternalClerkScriptProps } from '@clerk/shared/types';

Expand Down
11 changes: 10 additions & 1 deletion packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
TasksRedirectOptions,
} from '@clerk/shared/types';
import type { ClerkUIConstructor } from '@clerk/shared/ui';
import type { Appearance, ExtractAppearanceType, Ui } from '@clerk/ui/internal';
import type { Appearance, ExtractAppearanceType } from '@clerk/ui/internal';
import type React from 'react';

// Re-export types from @clerk/shared that are used by other modules
Expand All @@ -34,6 +34,15 @@ declare global {
}
}

// This is a redeclaration of the Ui type from @clerk/ui/internal, which prevents TypeScript from complaining that
// there is a type mismatch between the Ui type from @clerk/ui/internal and the bundled Ui type from @clerk/react.
export interface Ui<A = any> {
__brand?: '__clerkUI';
ClerkUI?: ClerkUIConstructor | Promise<ClerkUIConstructor>;
version?: string;
__appearanceType?: A;
}

/**
* @interface
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/react/tsdown.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default defineConfig((overrideOptions: Options) => {
clean: true,
minify: false,
sourcemap: true,
external: ['react', 'react-dom', '@clerk/ui/internal'],
external: ['react', 'react-dom'],
// Bundle @clerk/ui/register inline at build time so consumers don't need
// @clerk/ui as a dependency. The registration code sets up globalThis.__clerkSharedModules
// to enable @clerk/ui's shared variant to use the host app's React.
Expand Down
Loading