diff --git a/.changeset/short-mails-brush.md b/.changeset/short-mails-brush.md
new file mode 100644
index 00000000000..6ca8d0dfe7b
--- /dev/null
+++ b/.changeset/short-mails-brush.md
@@ -0,0 +1,20 @@
+---
+'@clerk/elements': patch
+---
+
+Add Elements `` component.
+
+```tsx
+import * as Clerk from '@clerk/elements/common';
+import NextLink from 'next/link';
+
+function SignInPage() {
+ return (
+ <>
+ Sign up
+
+ {url => Sign up}
+ >
+ );
+}
+```
diff --git a/.changeset/weak-hornets-hunt.md b/.changeset/weak-hornets-hunt.md
new file mode 100644
index 00000000000..b15f996d895
--- /dev/null
+++ b/.changeset/weak-hornets-hunt.md
@@ -0,0 +1,8 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/shared': patch
+'@clerk/clerk-react': patch
+'@clerk/types': patch
+---
+
+Expose internal `__internal_getOption` method from Clerk.
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index 9d6d253b9af..7c610a15d50 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -252,6 +252,10 @@ export class Clerk implements ClerkInterface {
return this.#options.standardBrowser || false;
}
+ public __internal_getOption(key: K): ClerkOptions[K] {
+ return this.#options[key];
+ }
+
public constructor(key: string, options?: DomainOrProxyUrl) {
key = (key || '').trim();
diff --git a/packages/elements/src/react/common/index.ts b/packages/elements/src/react/common/index.ts
index 72504ef7901..23898e39274 100644
--- a/packages/elements/src/react/common/index.ts
+++ b/packages/elements/src/react/common/index.ts
@@ -4,6 +4,7 @@ import 'client-only';
export { Field, FieldError, FieldState, GlobalError, Input, Label, Submit } from '~/react/common/form';
export { Connection, Icon } from '~/react/common/connections';
export { Loading } from '~/react/common/loading';
+export { Link } from '~/react/common/link';
export type {
FormFieldErrorProps,
diff --git a/packages/elements/src/react/common/link.tsx b/packages/elements/src/react/common/link.tsx
new file mode 100644
index 00000000000..74d2b6c9ed7
--- /dev/null
+++ b/packages/elements/src/react/common/link.tsx
@@ -0,0 +1,60 @@
+import { useClerk } from '@clerk/shared/react';
+import { useClerkRouter } from '@clerk/shared/router';
+import type { ClerkOptions } from '@clerk/types';
+import React from 'react';
+
+type Destination = 'sign-in' | 'sign-up';
+export interface LinkProps extends Omit, 'children'> {
+ navigate: Destination;
+ children: React.ReactNode | ((props: { url: string }) => React.ReactNode);
+}
+
+const paths: Record> = {
+ 'sign-in': 'signInUrl',
+ 'sign-up': 'signUpUrl',
+};
+
+/**
+ * The `` component is used to navigate between sign-in and sign-up flows.
+ *
+ * @param {Destination} navigate - The destination to navigate to.
+ *
+ * @example
+ * ```tsx
+ * Sign in
+ * ```
+ * @example
+ * ```tsx
+ *
+ * {({ url }) => (
+ * Sign in
+ * )}
+ *
+ */
+
+export function Link({ navigate, children, ...rest }: LinkProps) {
+ const router = useClerkRouter();
+ const clerk = useClerk();
+ const destinationUrl = router.makeDestinationUrlWithPreservedQueryParameters(
+ clerk.__internal_getOption(paths[navigate])!,
+ );
+
+ if (typeof children === 'function') {
+ return children({ url: destinationUrl });
+ }
+
+ return (
+ {
+ if (router) {
+ e.preventDefault();
+ router.push(destinationUrl);
+ }
+ }}
+ href={destinationUrl}
+ {...rest}
+ >
+ {children}
+
+ );
+}
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index 65684e4a91b..40c865118d9 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -11,6 +11,7 @@ import type {
AuthenticateWithMetamaskParams,
Clerk,
ClerkAuthenticateWithWeb3Params,
+ ClerkOptions,
ClientResource,
CreateOrganizationParams,
CreateOrganizationProps,
@@ -254,6 +255,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
return this.#proxyUrl || '';
}
+ public __internal_getOption(key: K): ClerkOptions[K] | undefined {
+ return this.clerkjs?.__internal_getOption(key);
+ }
+
constructor(options: IsomorphicClerkOptions) {
const { Clerk = null, publishableKey } = options || {};
this.#publishableKey = publishableKey;
diff --git a/packages/shared/src/router/router.ts b/packages/shared/src/router/router.ts
index e8c16969228..69cd41eb159 100644
--- a/packages/shared/src/router/router.ts
+++ b/packages/shared/src/router/router.ts
@@ -8,6 +8,7 @@ export const PRESERVED_QUERYSTRING_PARAMS = ['after_sign_in_url', 'after_sign_up
* Internal Clerk router, used by Clerk components to interact with the host's router.
*/
export type ClerkRouter = {
+ makeDestinationUrlWithPreservedQueryParameters: (path: string) => string;
/**
* The basePath the router is currently mounted on.
*/
@@ -132,6 +133,7 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/
}
return {
+ makeDestinationUrlWithPreservedQueryParameters,
child,
match,
mode: router.mode,
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index b9fea3d28fb..ffc92d5a403 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -106,6 +106,8 @@ export interface Clerk {
*/
loaded: boolean;
+ __internal_getOption(key: K): ClerkOptions[K];
+
frontendApi: string;
/** Clerk Publishable Key string. */