diff --git a/examples/nextjs-ssr/app/authenticated-app.tsx b/examples/nextjs-ssr/app/authenticated-app.tsx
new file mode 100644
index 000000000..ed6a7f0cb
--- /dev/null
+++ b/examples/nextjs-ssr/app/authenticated-app.tsx
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { multiFactor, sendEmailVerification, signOut } from "firebase/auth";
+import { useRouter } from "next/navigation";
+import { useUser } from "@/lib/firebase/hooks";
+import { auth } from "@/lib/firebase/clientApp";
+import { type User } from "firebase/auth";
+
+export function AuthenticatedApp({ initialUser }: { initialUser: User | null }) {
+ const user = useUser(initialUser);
+ const router = useRouter();
+
+ if (!user) {
+ return null;
+ }
+
+ const mfa = multiFactor(user);
+
+ return (
+
- );
+ if (currentUser) {
+ return ;
+ }
+
+ return ;
}
diff --git a/examples/nextjs-ssr/app/screens/email-link-auth-screen-w-oauth/page.tsx b/examples/nextjs-ssr/app/screens/email-link-auth-screen-w-oauth/page.tsx
index 2886c4113..97cb74e0d 100644
--- a/examples/nextjs-ssr/app/screens/email-link-auth-screen-w-oauth/page.tsx
+++ b/examples/nextjs-ssr/app/screens/email-link-auth-screen-w-oauth/page.tsx
@@ -16,12 +16,59 @@
"use client";
-import { EmailLinkAuthScreen, GoogleSignInButton } from "@invertase/firebaseui-react";
+import {
+ AppleSignInButton,
+ EmailLinkAuthScreen,
+ FacebookSignInButton,
+ GitHubSignInButton,
+ GoogleSignInButton,
+ MicrosoftSignInButton,
+ TwitterSignInButton,
+ OAuthButton,
+} from "@invertase/firebaseui-react";
+import { OAuthProvider } from "firebase/auth";
+import { useRouter } from "next/navigation";
export default function EmailLinkAuthScreenWithOAuthPage() {
+ const router = useRouter();
+
return (
-
+ {
+ alert("Email has been sent - please check your email");
+ }}
+ onSignIn={(credential) => {
+ console.log(credential);
+ router.push("/");
+ }}
+ >
+
+
+
+
+
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs-ssr/app/screens/layout.tsx b/examples/nextjs-ssr/app/screens/layout.tsx
new file mode 100644
index 000000000..392b2d1fb
--- /dev/null
+++ b/examples/nextjs-ssr/app/screens/layout.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Link from "next/link";
+
+export default function ScreensLayout({ children }: { children: React.ReactNode }) {
+ return (
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-handlers/page.tsx b/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-handlers/page.tsx
new file mode 100644
index 000000000..eed16bfc9
--- /dev/null
+++ b/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-handlers/page.tsx
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { SignUpAuthScreen } from "@invertase/firebaseui-react";
+import { useRouter } from "next/navigation";
+
+export default function SignUpAuthScreenWithHandlersPage() {
+ const router = useRouter();
+
+ return (
+ {
+ router.push("/screens/sign-in-auth-screen");
+ }}
+ onSignUp={(credential) => {
+ console.log(credential);
+ router.push("/");
+ }}
+ />
+ );
+}
diff --git a/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-oauth/page.tsx b/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-oauth/page.tsx
index 4c649f2e9..6c8c28749 100644
--- a/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-oauth/page.tsx
+++ b/examples/nextjs-ssr/app/screens/sign-up-auth-screen-w-oauth/page.tsx
@@ -16,12 +16,56 @@
"use client";
-import { GoogleSignInButton, SignUpAuthScreen } from "@invertase/firebaseui-react";
+import {
+ FacebookSignInButton,
+ GitHubSignInButton,
+ AppleSignInButton,
+ GoogleSignInButton,
+ SignUpAuthScreen,
+ TwitterSignInButton,
+ MicrosoftSignInButton,
+ OAuthButton,
+} from "@invertase/firebaseui-react";
+import { OAuthProvider } from "firebase/auth";
+import { useRouter } from "next/navigation";
export default function SignUpAuthScreenWithOAuthPage() {
+ const router = useRouter();
+
return (
-
+ {
+ console.log(credential);
+ router.push("/");
+ }}
+ >
+
+
+
+
+
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs-ssr/app/unauthenticated-app.tsx b/examples/nextjs-ssr/app/unauthenticated-app.tsx
new file mode 100644
index 000000000..0a61c76e5
--- /dev/null
+++ b/examples/nextjs-ssr/app/unauthenticated-app.tsx
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { MultiFactorAuthAssertionScreen, useUI } from "@invertase/firebaseui-react";
+import { routes } from "@/lib/routes";
+import Link from "next/link";
+
+export function UnauthenticatedApp() {
+ const ui = useUI();
+
+ // This can trigger if the user is not on a screen already, and gets an MFA challenge - e.g. on One-Tap sign in.
+ if (ui.multiFactorResolver) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ Welcome to Firebase UI, choose an example screen below to get started!
+
+
+
+ {routes.map((route) => (
+
+
+
{route.name}
+
{route.description}
+
+
+ →
+
+
+ ))}
+
+
+ );
+}
diff --git a/examples/nextjs-ssr/lib/components/pirate-toggle.tsx b/examples/nextjs-ssr/lib/components/pirate-toggle.tsx
new file mode 100644
index 000000000..d4b48e583
--- /dev/null
+++ b/examples/nextjs-ssr/lib/components/pirate-toggle.tsx
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { useUI } from "@invertase/firebaseui-react";
+import { enUs } from "@invertase/firebaseui-translations";
+import { pirate } from "../pirate";
+
+export function PirateToggle() {
+ const ui = useUI();
+ const isPirate = ui.locale.locale === "pirate";
+
+ return (
+
+ );
+}
diff --git a/examples/nextjs-ssr/lib/components/theme-toggle.tsx b/examples/nextjs-ssr/lib/components/theme-toggle.tsx
new file mode 100644
index 000000000..86a33ac45
--- /dev/null
+++ b/examples/nextjs-ssr/lib/components/theme-toggle.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+export function ThemeToggle() {
+ return (
+
+ );
+}
diff --git a/examples/nextjs-ssr/lib/pirate.ts b/examples/nextjs-ssr/lib/pirate.ts
new file mode 100644
index 000000000..aa92433ce
--- /dev/null
+++ b/examples/nextjs-ssr/lib/pirate.ts
@@ -0,0 +1,95 @@
+import { registerLocale } from "@invertase/firebaseui-translations";
+
+export const pirate = registerLocale("pirate", {
+ errors: {
+ userNotFound: "Arrr! No account found with this email address, matey",
+ wrongPassword: "Arrr! Incorrect password, ye scallywag",
+ invalidEmail: "Avast! Enter a valid email address, ye bilge rat",
+ userDisabled: "This account has been marooned, arrr!",
+ networkRequestFailed: "Can't connect to the server, ye land lubber! Check yer internet connection",
+ tooManyRequests: "Too many failed attempts, ye scurvy dog! Try again later",
+ missingVerificationCode: "Enter the verification code, ye scallywag",
+ emailAlreadyInUse: "An account already exists with this email, arrr!",
+ invalidCredential: "The credentials ye provided be invalid, matey",
+ weakPassword: "Ye password ain't long enough! It should be at least 8 characters",
+ unverifiedEmail: "Verify yer email address to continue, ye scallywag",
+ operationNotAllowed: "This operation ain't allowed, arrr! Contact support, matey",
+ invalidPhoneNumber: "The phone number be invalid, ye bilge rat",
+ missingPhoneNumber: "Provide a phone number, ye scallywag",
+ quotaExceeded: "SMS quota exceeded, arrr! Try again later, matey",
+ codeExpired: "The verification code has expired, ye scurvy dog",
+ captchaCheckFailed: "reCAPTCHA verification failed, arrr! Try again, matey",
+ missingVerificationId: "Complete the reCAPTCHA verification first, ye scallywag",
+ missingEmail: "Provide an email address, ye bilge rat",
+ invalidActionCode: "The password reset link be invalid or has expired, arrr!",
+ credentialAlreadyInUse: "An account already exists with this email, arrr! Sign in with that account, matey",
+ requiresRecentLogin: "This operation requires a recent login, ye scallywag! Sign in again",
+ providerAlreadyLinked: "This phone number be already linked to another account, arrr!",
+ invalidVerificationCode: "Invalid verification code, ye scurvy dog! Try again",
+ unknownError: "An unexpected error occurred, arrr!",
+ popupClosed: "The sign-in popup was closed, ye scallywag! Try again",
+ accountExistsWithDifferentCredential:
+ "An account already exists with this email, arrr! Sign in with the original provider, matey",
+ displayNameRequired: "Provide a display name, ye bilge rat",
+ secondFactorAlreadyInUse: "This phone number be already enrolled with this account, arrr!",
+ },
+ messages: {
+ passwordResetEmailSent: "Password reset email sent successfully, arrr!",
+ signInLinkSent: "Sign-in link sent successfully, matey!",
+ verificationCodeFirst: "Request a verification code first, ye scallywag",
+ checkEmailForReset: "Check yer email for password reset instructions, ye bilge rat",
+ dividerOr: "or",
+ termsAndPrivacy: "By continuing, ye agree to our {tos} and {privacy}, arrr!",
+ mfaSmsAssertionPrompt:
+ "A verification code will be sent to {phoneNumber} to complete the authentication process, matey.",
+ },
+ labels: {
+ emailAddress: "Email Address, ye bilge rat",
+ password: "Password, ye scallywag",
+ displayName: "Display Name, ye bilge rat",
+ forgotPassword: "Forgot Password, ye scallywag?",
+ signUp: "Sign Up, Matey",
+ signIn: "Sign In, Matey",
+ resetPassword: "Reset Password, ye scallywag",
+ createAccount: "Create Account, ye bilge rat",
+ backToSignIn: "Back to Sign In, ye scallywag",
+ signInWithPhone: "Sign in with Phone, ye scallywag",
+ phoneNumber: "Phone Number, ye bilge rat",
+ verificationCode: "Verification Code, ye scallywag",
+ sendCode: "Send Code, ye scallywag",
+ verifyCode: "Verify Code, ye scallywag",
+ signInWithGoogle: "Sign in with ye Google Account",
+ signInWithFacebook: "Sign in with ye Facebook Account",
+ signInWithApple: "Sign in with ye Apple Account",
+ signInWithMicrosoft: "Sign in with ye Microsoft Account",
+ signInWithGitHub: "Sign in with ye GitHub Account",
+ signInWithTwitter: "Sign in with ye X Account",
+ signInWithEmailLink: "Sign in with Email Link",
+ sendSignInLink: "Send Sign-in Link",
+ termsOfService: "Terms of Service",
+ privacyPolicy: "Privacy Policy",
+ resendCode: "Resend ye Code",
+ sending: "Firing...",
+ multiFactorEnrollment: "Multi-factor Enrrrrrrollment!",
+ multiFactorAssertion: "Multi-factor Authentication, arrr!",
+ mfaTotpVerification: "TOTP Verification, arrr!",
+ mfaSmsVerification: "SMS Verification, arrr!",
+ generateQrCode: "Generate ye QR Code",
+ },
+ prompts: {
+ noAccount: "Don't have an account, ye scallywag?",
+ haveAccount: "Already have an account, matey?",
+ enterEmailToReset: "Enter yer email address to reset yer password, ye bilge rat",
+ signInToAccount: "Sign in to yer account, matey",
+ smsVerificationPrompt: "Enter the verification code sent to yer phone number, ye scallywag",
+ enterDetailsToCreate: "Enter yer details to create a new account, ye bilge rat",
+ enterPhoneNumber: "Enter yer phone number, matey",
+ enterVerificationCode: "Enter the verification code, ye scallywag",
+ enterEmailForLink: "Enter yer email to receive a sign-in link, ye bilge rat",
+ mfaEnrollmentPrompt: "Select a new multi-factor enrollment method, arrr!",
+ mfaAssertionPrompt: "Complete the multi-factor authentication process, ye scallywag",
+ mfaAssertionFactorPrompt: "Choose a multi-factor authentication method, matey",
+ mfaTotpQrCodePrompt: "Scan this QR code with yer authenticator app, ye bilge rat",
+ mfaTotpEnrollmentVerificationPrompt: "Add the code generated by yer authenticator app, arrr!",
+ },
+});
diff --git a/examples/nextjs-ssr/lib/routes.ts b/examples/nextjs-ssr/lib/routes.ts
new file mode 100644
index 000000000..0e4fb01ca
--- /dev/null
+++ b/examples/nextjs-ssr/lib/routes.ts
@@ -0,0 +1,86 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const routes = [
+ {
+ name: "Sign In Screen",
+ description: "A sign in screen with email and password.",
+ path: "/screens/sign-in-auth-screen",
+ },
+ {
+ name: "Sign In Screen (with handlers)",
+ description: "A sign in screen with email and password, with forgot password and register handlers.",
+ path: "/screens/sign-in-auth-screen-w-handlers",
+ },
+ {
+ name: "Sign In Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-in-auth-screen-w-oauth",
+ },
+ {
+ name: "Sign Up Screen",
+ description: "A sign up screen with email and password.",
+ path: "/screens/sign-up-auth-screen",
+ },
+ {
+ name: "Sign Up Screen (with handlers)",
+ description: "A sign up screen with email and password, sign in handlers.",
+ path: "/screens/sign-up-auth-screen-w-handlers",
+ },
+ {
+ name: "Sign Up Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-up-auth-screen-w-oauth",
+ },
+ {
+ name: "Email Link Auth Screen",
+ description: "A screen allowing a user to send an email link for sign in.",
+ path: "/screens/email-link-auth-screen",
+ },
+ {
+ name: "Email Link Auth Screen (with OAuth)",
+ description: "A screen allowing a user to send an email link for sign in, with oAuth buttons.",
+ path: "/screens/email-link-auth-screen-w-oauth",
+ },
+ {
+ name: "Forgot Password Screen",
+ description: "A screen allowing a user to reset their password.",
+ path: "/screens/forgot-password-auth-screen",
+ },
+ {
+ name: "OAuth Screen",
+ description: "A screen which allows a user to sign in with OAuth only.",
+ path: "/screens/oauth-screen",
+ },
+ {
+ name: "Phone Auth Screen",
+ description: "A screen allowing a user to sign in with a phone number.",
+ path: "/screens/phone-auth-screen",
+ },
+ {
+ name: "Phone Auth Screen (with OAuth)",
+ description: "A screen allowing a user to sign in with a phone number, with oAuth buttons.",
+ path: "/screens/phone-auth-screen-w-oauth",
+ },
+] as const;
+
+export const hiddenRoutes = [
+ {
+ name: "MFA Enrollment Screen",
+ description: "A screen allowing a user to enroll in multi-factor authentication.",
+ path: "/screens/mfa-enrollment-screen",
+ },
+] as const;
diff --git a/examples/nextjs-ssr/package.json b/examples/nextjs-ssr/package.json
index e7ee1a21c..6fad1510d 100644
--- a/examples/nextjs-ssr/package.json
+++ b/examples/nextjs-ssr/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev --turbopack",
+ "dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
diff --git a/examples/nextjs-ssr/public/firebase-logo-inverted.png b/examples/nextjs-ssr/public/firebase-logo-inverted.png
new file mode 100644
index 000000000..b6f4ef80a
Binary files /dev/null and b/examples/nextjs-ssr/public/firebase-logo-inverted.png differ
diff --git a/examples/nextjs-ssr/public/firebase-logo.png b/examples/nextjs-ssr/public/firebase-logo.png
new file mode 100644
index 000000000..1cf731440
Binary files /dev/null and b/examples/nextjs-ssr/public/firebase-logo.png differ
diff --git a/examples/nextjs/app/globals.css b/examples/nextjs/app/globals.css
index 7e62d3ba8..1fda7fe64 100644
--- a/examples/nextjs/app/globals.css
+++ b/examples/nextjs/app/globals.css
@@ -15,7 +15,25 @@
*/
@import "tailwindcss";
+@custom-variant dark (&:where(.dark, .dark *));
@import "@invertase/firebaseui-styles/tailwind";
+/* Prevent flash by hiding content until theme is loaded */
+html:not(.theme-loaded) body {
+ visibility: hidden;
+}
+
+html.theme-loaded body {
+ visibility: visible;
+}
+
+.fui-provider__button[data-provider="oidc.line"][data-themed="true"] {
+ --line-primary: #07B53B;
+ --color-primary: var(--line-primary);
+ --color-primary-hover: --alpha(var(--line-primary) / 85%);
+ --color-primary-surface: #FFFFFF;
+ --color-border: var(--line-primary);
+}
+
/* @import "@invertase/firebaseui-styles/themes/dark.css"; */
/* @import "@invertase/firebaseui-styles/themes/brutalist.css"; */
diff --git a/examples/nextjs/app/layout.tsx b/examples/nextjs/app/layout.tsx
index 9c7acf604..6fb5a1dc5 100644
--- a/examples/nextjs/app/layout.tsx
+++ b/examples/nextjs/app/layout.tsx
@@ -14,24 +14,13 @@
* limitations under the License.
*/
-// import { getCurrentUser } from "@/lib/firebase/serverApp";
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import Script from "next/script";
import { FirebaseUIProviderHoc } from "../lib/firebase/ui";
-// import { Header } from "@/lib/components/header";
+import { ThemeToggle } from "../lib/components/theme-toggle";
+import { PirateToggle } from "../lib/components/pirate-toggle";
import "./globals.css";
-// import { useUser } from "@/lib/firebase/hooks";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
export const metadata: Metadata = {
title: "Create Next App",
@@ -43,13 +32,36 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
- // const user = await useUser();
-
return (
-
-
- {/* */}
- {children}
+
+
+
+
+
+
+ {children}
+
);
diff --git a/examples/nextjs/app/page.tsx b/examples/nextjs/app/page.tsx
index 3e60dfe04..69ede49ac 100644
--- a/examples/nextjs/app/page.tsx
+++ b/examples/nextjs/app/page.tsx
@@ -16,75 +16,122 @@
"use client";
+import { MultiFactorAuthAssertionScreen, useUI } from "@invertase/firebaseui-react";
+import { multiFactor, sendEmailVerification, signOut } from "firebase/auth";
+import { useRouter } from "next/navigation";
import { useUser } from "@/lib/firebase/hooks";
+import { auth } from "@/lib/firebase/clientApp";
+import { routes } from "@/lib/routes";
import Link from "next/link";
export default function Home() {
const user = useUser();
+ const ui = useUI();
+ const router = useRouter();
+
+ if (user) {
+ return ;
+ }
+
+ return ;
+}
+
+function UnauthenticatedApp({ ui }: { ui: ReturnType }) {
+ // This can trigger if the user is not on a screen already, and gets an MFA challenge - e.g. on One-Tap sign in.
+ if (ui.multiFactorResolver) {
+ return ;
+ }
return (
-
-
Firebase UI Demo
-
{user &&
Welcome: {user.email || user.phoneNumber}
}
-
-
Auth Screens
-
-
-
- Sign In Auth Screen
-
-
-
-
- Sign In Auth Screen with Handlers
-
-
-
-
- Sign In Auth Screen with OAuth
-
-
-
-
- Email Link Auth Screen
-
-
-
-
- Email Link Auth Screen with OAuth
-
-
-
-
- Phone Auth Screen
-
-
-
-
- Phone Auth Screen with OAuth
-
-
-
-
- Sign Up Auth Screen
-
-
-
-
- Sign Up Auth Screen with OAuth
-
-
-
-
- OAuth Screen
-
-
-
-
- Password Reset Screen
-
-
-
+
+
+
+
+
+ Welcome to Firebase UI, choose an example screen below to get started!
+
);
diff --git a/examples/nextjs/app/screens/email-link-auth-screen-w-oauth/page.tsx b/examples/nextjs/app/screens/email-link-auth-screen-w-oauth/page.tsx
index 2886c4113..97cb74e0d 100644
--- a/examples/nextjs/app/screens/email-link-auth-screen-w-oauth/page.tsx
+++ b/examples/nextjs/app/screens/email-link-auth-screen-w-oauth/page.tsx
@@ -16,12 +16,59 @@
"use client";
-import { EmailLinkAuthScreen, GoogleSignInButton } from "@invertase/firebaseui-react";
+import {
+ AppleSignInButton,
+ EmailLinkAuthScreen,
+ FacebookSignInButton,
+ GitHubSignInButton,
+ GoogleSignInButton,
+ MicrosoftSignInButton,
+ TwitterSignInButton,
+ OAuthButton,
+} from "@invertase/firebaseui-react";
+import { OAuthProvider } from "firebase/auth";
+import { useRouter } from "next/navigation";
export default function EmailLinkAuthScreenWithOAuthPage() {
+ const router = useRouter();
+
return (
-
+ {
+ alert("Email has been sent - please check your email");
+ }}
+ onSignIn={(credential) => {
+ console.log(credential);
+ router.push("/");
+ }}
+ >
+
+
+
+
+
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs/app/screens/layout.tsx b/examples/nextjs/app/screens/layout.tsx
new file mode 100644
index 000000000..392b2d1fb
--- /dev/null
+++ b/examples/nextjs/app/screens/layout.tsx
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Link from "next/link";
+
+export default function ScreensLayout({ children }: { children: React.ReactNode }) {
+ return (
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs/app/screens/sign-up-auth-screen-w-handlers/page.tsx b/examples/nextjs/app/screens/sign-up-auth-screen-w-handlers/page.tsx
new file mode 100644
index 000000000..eed16bfc9
--- /dev/null
+++ b/examples/nextjs/app/screens/sign-up-auth-screen-w-handlers/page.tsx
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { SignUpAuthScreen } from "@invertase/firebaseui-react";
+import { useRouter } from "next/navigation";
+
+export default function SignUpAuthScreenWithHandlersPage() {
+ const router = useRouter();
+
+ return (
+ {
+ router.push("/screens/sign-in-auth-screen");
+ }}
+ onSignUp={(credential) => {
+ console.log(credential);
+ router.push("/");
+ }}
+ />
+ );
+}
diff --git a/examples/nextjs/app/screens/sign-up-auth-screen-w-oauth/page.tsx b/examples/nextjs/app/screens/sign-up-auth-screen-w-oauth/page.tsx
index 4c649f2e9..6c8c28749 100644
--- a/examples/nextjs/app/screens/sign-up-auth-screen-w-oauth/page.tsx
+++ b/examples/nextjs/app/screens/sign-up-auth-screen-w-oauth/page.tsx
@@ -16,12 +16,56 @@
"use client";
-import { GoogleSignInButton, SignUpAuthScreen } from "@invertase/firebaseui-react";
+import {
+ FacebookSignInButton,
+ GitHubSignInButton,
+ AppleSignInButton,
+ GoogleSignInButton,
+ SignUpAuthScreen,
+ TwitterSignInButton,
+ MicrosoftSignInButton,
+ OAuthButton,
+} from "@invertase/firebaseui-react";
+import { OAuthProvider } from "firebase/auth";
+import { useRouter } from "next/navigation";
export default function SignUpAuthScreenWithOAuthPage() {
+ const router = useRouter();
+
return (
-
+ {
+ console.log(credential);
+ router.push("/");
+ }}
+ >
+
+
+
+
+
+
);
}
+
+function LineSignInButton() {
+ const provider = new OAuthProvider("oidc.line");
+
+ return (
+
+
+ Sign in with Line
+
+ );
+}
diff --git a/examples/nextjs/lib/components/pirate-toggle.tsx b/examples/nextjs/lib/components/pirate-toggle.tsx
new file mode 100644
index 000000000..d4b48e583
--- /dev/null
+++ b/examples/nextjs/lib/components/pirate-toggle.tsx
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+import { useUI } from "@invertase/firebaseui-react";
+import { enUs } from "@invertase/firebaseui-translations";
+import { pirate } from "../pirate";
+
+export function PirateToggle() {
+ const ui = useUI();
+ const isPirate = ui.locale.locale === "pirate";
+
+ return (
+
+ );
+}
diff --git a/examples/nextjs/lib/components/theme-toggle.tsx b/examples/nextjs/lib/components/theme-toggle.tsx
new file mode 100644
index 000000000..86a33ac45
--- /dev/null
+++ b/examples/nextjs/lib/components/theme-toggle.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use client";
+
+export function ThemeToggle() {
+ return (
+
+ );
+}
diff --git a/examples/nextjs/lib/pirate.ts b/examples/nextjs/lib/pirate.ts
new file mode 100644
index 000000000..aa92433ce
--- /dev/null
+++ b/examples/nextjs/lib/pirate.ts
@@ -0,0 +1,95 @@
+import { registerLocale } from "@invertase/firebaseui-translations";
+
+export const pirate = registerLocale("pirate", {
+ errors: {
+ userNotFound: "Arrr! No account found with this email address, matey",
+ wrongPassword: "Arrr! Incorrect password, ye scallywag",
+ invalidEmail: "Avast! Enter a valid email address, ye bilge rat",
+ userDisabled: "This account has been marooned, arrr!",
+ networkRequestFailed: "Can't connect to the server, ye land lubber! Check yer internet connection",
+ tooManyRequests: "Too many failed attempts, ye scurvy dog! Try again later",
+ missingVerificationCode: "Enter the verification code, ye scallywag",
+ emailAlreadyInUse: "An account already exists with this email, arrr!",
+ invalidCredential: "The credentials ye provided be invalid, matey",
+ weakPassword: "Ye password ain't long enough! It should be at least 8 characters",
+ unverifiedEmail: "Verify yer email address to continue, ye scallywag",
+ operationNotAllowed: "This operation ain't allowed, arrr! Contact support, matey",
+ invalidPhoneNumber: "The phone number be invalid, ye bilge rat",
+ missingPhoneNumber: "Provide a phone number, ye scallywag",
+ quotaExceeded: "SMS quota exceeded, arrr! Try again later, matey",
+ codeExpired: "The verification code has expired, ye scurvy dog",
+ captchaCheckFailed: "reCAPTCHA verification failed, arrr! Try again, matey",
+ missingVerificationId: "Complete the reCAPTCHA verification first, ye scallywag",
+ missingEmail: "Provide an email address, ye bilge rat",
+ invalidActionCode: "The password reset link be invalid or has expired, arrr!",
+ credentialAlreadyInUse: "An account already exists with this email, arrr! Sign in with that account, matey",
+ requiresRecentLogin: "This operation requires a recent login, ye scallywag! Sign in again",
+ providerAlreadyLinked: "This phone number be already linked to another account, arrr!",
+ invalidVerificationCode: "Invalid verification code, ye scurvy dog! Try again",
+ unknownError: "An unexpected error occurred, arrr!",
+ popupClosed: "The sign-in popup was closed, ye scallywag! Try again",
+ accountExistsWithDifferentCredential:
+ "An account already exists with this email, arrr! Sign in with the original provider, matey",
+ displayNameRequired: "Provide a display name, ye bilge rat",
+ secondFactorAlreadyInUse: "This phone number be already enrolled with this account, arrr!",
+ },
+ messages: {
+ passwordResetEmailSent: "Password reset email sent successfully, arrr!",
+ signInLinkSent: "Sign-in link sent successfully, matey!",
+ verificationCodeFirst: "Request a verification code first, ye scallywag",
+ checkEmailForReset: "Check yer email for password reset instructions, ye bilge rat",
+ dividerOr: "or",
+ termsAndPrivacy: "By continuing, ye agree to our {tos} and {privacy}, arrr!",
+ mfaSmsAssertionPrompt:
+ "A verification code will be sent to {phoneNumber} to complete the authentication process, matey.",
+ },
+ labels: {
+ emailAddress: "Email Address, ye bilge rat",
+ password: "Password, ye scallywag",
+ displayName: "Display Name, ye bilge rat",
+ forgotPassword: "Forgot Password, ye scallywag?",
+ signUp: "Sign Up, Matey",
+ signIn: "Sign In, Matey",
+ resetPassword: "Reset Password, ye scallywag",
+ createAccount: "Create Account, ye bilge rat",
+ backToSignIn: "Back to Sign In, ye scallywag",
+ signInWithPhone: "Sign in with Phone, ye scallywag",
+ phoneNumber: "Phone Number, ye bilge rat",
+ verificationCode: "Verification Code, ye scallywag",
+ sendCode: "Send Code, ye scallywag",
+ verifyCode: "Verify Code, ye scallywag",
+ signInWithGoogle: "Sign in with ye Google Account",
+ signInWithFacebook: "Sign in with ye Facebook Account",
+ signInWithApple: "Sign in with ye Apple Account",
+ signInWithMicrosoft: "Sign in with ye Microsoft Account",
+ signInWithGitHub: "Sign in with ye GitHub Account",
+ signInWithTwitter: "Sign in with ye X Account",
+ signInWithEmailLink: "Sign in with Email Link",
+ sendSignInLink: "Send Sign-in Link",
+ termsOfService: "Terms of Service",
+ privacyPolicy: "Privacy Policy",
+ resendCode: "Resend ye Code",
+ sending: "Firing...",
+ multiFactorEnrollment: "Multi-factor Enrrrrrrollment!",
+ multiFactorAssertion: "Multi-factor Authentication, arrr!",
+ mfaTotpVerification: "TOTP Verification, arrr!",
+ mfaSmsVerification: "SMS Verification, arrr!",
+ generateQrCode: "Generate ye QR Code",
+ },
+ prompts: {
+ noAccount: "Don't have an account, ye scallywag?",
+ haveAccount: "Already have an account, matey?",
+ enterEmailToReset: "Enter yer email address to reset yer password, ye bilge rat",
+ signInToAccount: "Sign in to yer account, matey",
+ smsVerificationPrompt: "Enter the verification code sent to yer phone number, ye scallywag",
+ enterDetailsToCreate: "Enter yer details to create a new account, ye bilge rat",
+ enterPhoneNumber: "Enter yer phone number, matey",
+ enterVerificationCode: "Enter the verification code, ye scallywag",
+ enterEmailForLink: "Enter yer email to receive a sign-in link, ye bilge rat",
+ mfaEnrollmentPrompt: "Select a new multi-factor enrollment method, arrr!",
+ mfaAssertionPrompt: "Complete the multi-factor authentication process, ye scallywag",
+ mfaAssertionFactorPrompt: "Choose a multi-factor authentication method, matey",
+ mfaTotpQrCodePrompt: "Scan this QR code with yer authenticator app, ye bilge rat",
+ mfaTotpEnrollmentVerificationPrompt: "Add the code generated by yer authenticator app, arrr!",
+ },
+});
diff --git a/examples/nextjs/lib/routes.ts b/examples/nextjs/lib/routes.ts
new file mode 100644
index 000000000..0e4fb01ca
--- /dev/null
+++ b/examples/nextjs/lib/routes.ts
@@ -0,0 +1,86 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const routes = [
+ {
+ name: "Sign In Screen",
+ description: "A sign in screen with email and password.",
+ path: "/screens/sign-in-auth-screen",
+ },
+ {
+ name: "Sign In Screen (with handlers)",
+ description: "A sign in screen with email and password, with forgot password and register handlers.",
+ path: "/screens/sign-in-auth-screen-w-handlers",
+ },
+ {
+ name: "Sign In Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-in-auth-screen-w-oauth",
+ },
+ {
+ name: "Sign Up Screen",
+ description: "A sign up screen with email and password.",
+ path: "/screens/sign-up-auth-screen",
+ },
+ {
+ name: "Sign Up Screen (with handlers)",
+ description: "A sign up screen with email and password, sign in handlers.",
+ path: "/screens/sign-up-auth-screen-w-handlers",
+ },
+ {
+ name: "Sign Up Screen (with OAuth)",
+ description: "A sign in screen with email and password, with oAuth buttons.",
+ path: "/screens/sign-up-auth-screen-w-oauth",
+ },
+ {
+ name: "Email Link Auth Screen",
+ description: "A screen allowing a user to send an email link for sign in.",
+ path: "/screens/email-link-auth-screen",
+ },
+ {
+ name: "Email Link Auth Screen (with OAuth)",
+ description: "A screen allowing a user to send an email link for sign in, with oAuth buttons.",
+ path: "/screens/email-link-auth-screen-w-oauth",
+ },
+ {
+ name: "Forgot Password Screen",
+ description: "A screen allowing a user to reset their password.",
+ path: "/screens/forgot-password-auth-screen",
+ },
+ {
+ name: "OAuth Screen",
+ description: "A screen which allows a user to sign in with OAuth only.",
+ path: "/screens/oauth-screen",
+ },
+ {
+ name: "Phone Auth Screen",
+ description: "A screen allowing a user to sign in with a phone number.",
+ path: "/screens/phone-auth-screen",
+ },
+ {
+ name: "Phone Auth Screen (with OAuth)",
+ description: "A screen allowing a user to sign in with a phone number, with oAuth buttons.",
+ path: "/screens/phone-auth-screen-w-oauth",
+ },
+] as const;
+
+export const hiddenRoutes = [
+ {
+ name: "MFA Enrollment Screen",
+ description: "A screen allowing a user to enroll in multi-factor authentication.",
+ path: "/screens/mfa-enrollment-screen",
+ },
+] as const;
diff --git a/examples/nextjs/public/firebase-logo-inverted.png b/examples/nextjs/public/firebase-logo-inverted.png
new file mode 100644
index 000000000..b6f4ef80a
Binary files /dev/null and b/examples/nextjs/public/firebase-logo-inverted.png differ
diff --git a/examples/nextjs/public/firebase-logo.png b/examples/nextjs/public/firebase-logo.png
new file mode 100644
index 000000000..1cf731440
Binary files /dev/null and b/examples/nextjs/public/firebase-logo.png differ
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 1bb61b362..b94190c11 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -27,6 +27,6 @@ export * from "./register-framework";
export * from "./schemas";
export * from "./translations";
-if (import.meta.env.PROD) {
+if (import.meta.env?.PROD) {
registerFramework("core", pkgJson.version);
}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index b79e63a60..b0d565497 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -24,6 +24,6 @@ export { PolicyContext } from "./components/policies";
export { FirebaseUIProvider, type FirebaseUIProviderProps } from "./context";
export * from "./hooks";
-if (import.meta.env.PROD) {
+if (import.meta.env?.PROD) {
registerFramework("react", pkgJson.version);
}