Components
Import the styles once in your root layout — import "@authflowkit/ui/styles.css" — and put the provider from the quickstart above every component. Every link and redirect is a prop, and every piece of text can be replaced through labels:
<SignIn labels={{ heading: "Welcome back", submit: "Log in" }} /><SignIn />
Email and password sign-in, with links to sign up and to reset a forgotten password. When the user has two-factor on, it asks for their code in place.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
The default state. With two-factor on, the code step replaces this form in place.
| Prop | Type | Default | What it does |
|---|---|---|---|
redirectTo | string | "/" | Where to send the user after they sign in. |
forgotPasswordUrl | string | "/reset" | Where the forgot-password link goes. The reset email lands on /reset too, so one page serves both. |
signUpUrl | string | "/sign-up" | Where the sign-up link goes. |
onSignedIn | (user: AuthUser) => void | — | Called instead of a full page load to redirectTo. Pass it to navigate with your router. |
onTotpRequired | (challenge: TotpChallengeHandoff) => void | — | Called when the password was right but the user has two-factor on, with the token for the code step. SignIn then leaves that step to you. |
labels | Partial<SignInLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
headingemailLabelpasswordLabelsubmitforgotPasswordnoAccountsignUpresendVerificationverificationSent
<SignUp />
Creates an account, then replaces the form with a check-your-email message. The message is the same whether or not the address was already registered, so the form never reveals who has an account.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
The default state. After a successful submit the form is replaced by a check-your-email message.
| Prop | Type | Default | What it does |
|---|---|---|---|
signInUrl | string | "/sign-in" | Where the sign-in link goes. |
labels | Partial<SignUpLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
headingemailLabelpasswordLabelpasswordHintsubmithaveAccountsignInsentHeadingsentBodyinvalidEmailshortPassword
<VerifyEmail />
Confirms the address from the emailed link and signs the user in. An expired, used or unknown link gets one clear message and a button to send a new one.
Mount it at /verify. AuthFlow’s emails link there, and the path is fixed.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
The state when the link arrived without its token. With a valid token it shows progress, then success.
| Prop | Type | Default | What it does |
|---|---|---|---|
token | string | — | The token. Read from ?token= in the URL when you leave it out. |
redirectTo | string | "/" | Where to send the user once the address is confirmed. |
onVerified | () => void | — | Called instead of navigating to redirectTo. |
labels | Partial<VerifyEmailLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
workingHeadingworkingBodysuccessHeadingsuccessBodyfailedHeadingnoTokenHeadingnoTokenBodyemailLabelresendresentcontinueLabel
<ResetPassword />
Both halves of a password reset on one route. With no token in the URL it asks for an email address; opened from the emailed link, it asks for a new password. Resetting signs out every session and does not sign the user in.
Mount it at /reset. AuthFlow’s emails link there, and the path is fixed.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
Request mode, shown when there is no token in the URL. Opened from the email it asks for a new password instead.
| Prop | Type | Default | What it does |
|---|---|---|---|
mode | "request" | "confirm" | — | Force one half. Worked out from the URL when you leave it out. |
token | string | — | The reset token. Read from ?token= in the URL when you leave it out. |
signInUrl | string | "/sign-in" | Where to send the user after they set a new password. |
labels | Partial<ResetPasswordLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
requestHeadingrequestBodyemailLabelrequestSubmitsentHeadingsentBodyconfirmHeadingpasswordLabelpasswordHintconfirmSubmitdoneHeadingdoneBodynoTokenHeadingnoTokenBodyrequestNewLinkbackToSignIninvalidEmailshortPassword
<UserButton />
Shows who is signed in, with a sign-out action. Renders nothing when nobody is signed in, and a placeholder while the session loads.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
Signed in, with the menu open to a sample address. Signed out it renders nothing at all.
| Prop | Type | Default | What it does |
|---|---|---|---|
signOutTo | string | "/" | Where to go after signing out. |
onSignedOut | () => void | — | Called instead of navigating to signOutTo, once the user has signed out. |
labels | Partial<UserButtonLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
signOutmenuLabel
<TotpChallenge />
The six-digit code step, with a switch to a recovery code. SignIn shows it for you; use it on its own only in a sign-in form of your own.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
The default state. SignIn shows this for you; mount it yourself only inside a sign-in form of your own.
| Prop | Type | Default | What it does |
|---|---|---|---|
challengeToken | string | required | The token from a correct password. Held in memory for this step only. |
redirectTo | string | "/" | Where to send the user once the code is accepted. |
signInUrl | string | "/sign-in" | Where to start again if the step expires. |
onVerified | (user: AuthUser) => void | — | Called instead of navigating to redirectTo. |
labels | Partial<TotpChallengeLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
headingbodycodeLabelrecoveryLabelsubmituseRecoveryuseAuthenticatorexpiredHeadingexpiredBodybackToSignInlowCodes
<EnableTotp />
Lets a signed-in user turn two-factor on or off: a QR code and a manual key, a confirmation code, and ten recovery codes shown exactly once. Renders nothing when nobody is signed in.
Live preview — type in it, press the buttons. Nothing is sent anywhere.
Two-factor authentication
Add a second step to signing in. You will need an authenticator app such as Google Authenticator, Authy or 1Password.
Two-factor off, for a signed-in user. Turning it on reveals a QR code, then the recovery codes, once.
| Prop | Type | Default | What it does |
|---|---|---|---|
labels | Partial<EnableTotpLabels> | — | Replace any of the text listed below. |
className | string | — | An extra class name for styling. |
Text you can replace with labels
offHeadingoffBodystartscanHeadingscanBodymanualLabelcodeLabelconfirmSubmitcodesHeadingcodesWarningcopyCodescopieddownloadCodescodesDoneonHeadingonBodydisabledisableHeadingdisableWhypasswordLabeldisableSubmitcancel
Hooks and helpers
| Name | Import from | What it does |
|---|---|---|
<AuthFlowProvider> | @authflowkit/sdk | Wrap your app with it once, in app/layout.tsx. It reads the session and shares it with every component and hook below it. |
useUser() | @authflowkit/sdk | For Client Components. Returns status ("loading", "signedIn" or "signedOut"), user, error and refresh(). A failed read is signedOut with error set, so an outage never looks like a sign-out. |
currentUser() | @authflowkit/sdk/server | For Server Components and route handlers. Returns the signed-in user or null, and throws only when AuthFlow could not be reached. It checks the session with AuthFlow, so it is the check to rely on. |
createAuthFlowHandler() | @authflowkit/sdk/server | Builds the proxy route. Reads AUTHFLOW_API_URL and AUTHFLOW_SECRET_KEY from the environment; pass { apiUrl, secretKey, cookieName } only to override them. |
authMiddleware() | @authflowkit/sdk/middleware | Sends visitors without a session cookie to sign-in. Options: publicRoutes (exact paths, or a prefix ending in *), signInUrl (default "/sign-in"), cookieName, and returnToParam (default "returnTo"). A routing helper, not the security boundary. |
AuthFlowError, isAuthFlowError() | @authflowkit/sdk | The only error any call rejects with. It has a stable code, and retryAfterSeconds when the code is RATE_LIMITED. Test with isAuthFlowError(e) rather than instanceof. |
Theming
Dark is the default and a light theme ships alongside it. To let visitors switch, set the theme before the first paint and add a toggle:
// app/layout.tsx
import { AuthFlowProvider } from "@authflowkit/sdk";
import { ThemeProvider, ThemeScript } from "@authflowkit/ui";
import "@authflowkit/ui/styles.css";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<ThemeScript />
</head>
<body>
<ThemeProvider>
<AuthFlowProvider>{children}</AuthFlowProvider>
</ThemeProvider>
</body>
</html>
);
}| Name | Import from | What it does |
|---|---|---|
<ThemeScript /> | @authflowkit/ui | Goes in <head>. Sets the theme before the first paint, so the page never flashes the wrong one. Add suppressHydrationWarning to <html>, because the script changes an attribute there before React loads. |
<ThemeProvider> | @authflowkit/ui | Wraps your app. defaultTheme is "dark", "light" or "system" (default "dark") and applies until the visitor chooses; their choice is remembered and always wins. Give ThemeScript the same defaultTheme. |
<ThemeToggle /> | @authflowkit/ui | A button that switches between dark and light. Props: label (default "Light theme") and className. |
useTheme() | @authflowkit/ui | The current theme ("dark", "light", or null until the page has mounted), plus setTheme() and toggleTheme(), for building your own control. |
Every colour, radius and spacing value is a CSS custom property whose name starts with --af-. Set any of them on :root after importing the styles to restyle every component. If you change --af-primary, check that --af-primary-ink still reads clearly on top of it.