Skip to content
SLICES / TECHNICAL FOLIO Nº 001FULL-STACK ASSEMBLIES — OPEN SOURCE

Headless / Account

Two-factor authentication

An authenticator app for the signed-in account: setting one up, the recovery codes, replacing or removing it, and the second-factor step of signing in. A subpath of its own, installed only where an app offers it. It exports SignIn and User as the auth namespaces with its parts added, so a screen writes SignIn.TwoFactorCode and User.TwoFactor.Root, importing both from here.

38 public parts · 3examples

@slices.sh/auth/headless/two-factorView as Markdown ↗

Your design. Our behavior.

Explore the tree to see what each part controls. Gates reveal the relevant state; roots outline their scope.

Behavior / inputGateGenericRenderer · not headlessScope
COMPONENT ANATOMYHover · focus · tap
ONE POSSIBLE DESIGNStatic preview

User.Root · Scope

User.RootSays which auth the User parts below read, and whether they may do without it. The parts need no root: left out, they read the nearest auth root's config, or "default", and render nothing without auth. `<User.Root enabled="optional">` is for the one who wants that said in the code.

Installation

Choose what your component needs. Run these commands in an initialized Next.js app.

0

Before you start

The steps below run in a Next.js app with shadcn set up (it has a components.json). In an existing Next.js app, such as one from create-next-app, set shadcn up first:

npx shadcn@latest init -t next -b base -p nova

Or start fresh: the slices CLI creates the app and sets it up.

npx slices.sh install
1

Install the service registry

Adds services/index.ts, which import "@/services" loads. A provider setup writes the same file, so skip this if your app has one.

npx add services
2

Install the headless library

npm

Add the React parts to your app, then compose them with your own elements.

npm install @slices.sh/auth@^0.1.10

Import from @slices.sh/auth/headless/two-factor.

3

Install the service

Connect the parts to a provider. Skip this step if this service is already configured in your app.

Provider
Database
Authentication methods
Account capabilities
npx add auth.betterauth --two-factor

Adds service configuration and API routes to your app, with the provider dependencies. For a headless composition, add import "@/services" after "use client".

The slices CLI installs the service and provisions its required resources in one command.

Set BETTER_AUTH_URL to your app origin. Memory is for local development; use a persistent database for deployment. Neon and Supabase also require the migration script printed by the installer.

Provider setup notes
  • BETTER_AUTH_SECRET signs every session and is generated into .env.local while this installs (32 random bytes, base64, nothing leaves the machine). Changing it signs everyone out; in production set the same one on every instance.
  • The public origin (OAuth callbacks, the passkey domain) is the one each request comes to in development, and the deployment's own on Vercel. Elsewhere, or behind a proxy or a custom domain, set BETTER_AUTH_URL, such as https://acme.com.

Start small

Parts bring the behavior. Use their default elements, or supply your own with render.

example.tsx
"use client"import "@/services"import { User } from "@slices.sh/auth/headless/two-factor"export function Example() {  return (    <User.Root>      <User.TwoFactor.Root>        <User.TwoFactor.Step name="off">          <User.TwoFactor.Begin>Enable two-factor</User.TwoFactor.Begin>        </User.TwoFactor.Step>        <User.TwoFactor.Step name="confirm">          <User.TwoFactor.Form>            <User.TwoFactor.Password aria-label="Current password" />            <User.TwoFactor.Confirm>Continue</User.TwoFactor.Confirm>          </User.TwoFactor.Form>        </User.TwoFactor.Step>        <User.TwoFactor.Step name="scan">          <User.TwoFactor.Form>            <User.TwoFactor.Qr />            <User.TwoFactor.Secret />            <User.TwoFactor.Code aria-label="Authenticator code" />            <User.TwoFactor.Enable>Verify</User.TwoFactor.Enable>          </User.TwoFactor.Form>        </User.TwoFactor.Step>        <User.TwoFactor.Step name="verified">          <User.TwoFactor.ShowCodes>            Save recovery codes          </User.TwoFactor.ShowCodes>        </User.TwoFactor.Step>        <User.TwoFactor.Step name="codes">          <User.TwoFactor.BackupCode />          <User.TwoFactor.CopyCodes>Copy codes</User.TwoFactor.CopyCodes>          <label>            <User.TwoFactor.CodesStored />I saved these codes          </label>          <User.TwoFactor.Finish>Done</User.TwoFactor.Finish>        </User.TwoFactor.Step>        <User.TwoFactor.Step name="on">          Your account is protected.        </User.TwoFactor.Step>        <User.TwoFactor.Error />      </User.TwoFactor.Root>    </User.Root>  )}

Enable password and two-factor. This wizard assumes the signed-in account already has a password.

API reference

Every public part used by this experience is listed below. Gates render children without adding an element. Element parts accept native element props and render; function children receive the state declared in their props. Scopes provide context rather than a visual element.

User.TwoFactor.Dialogcontent

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Required scope

User.TwoFactor.Dialog requires User.TwoFactor.Root.

Props

2 documented props

User.TwoFactor.Dialog props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/dialog.tsx

User.TwoFactor.Statusgate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Props

2 documented props

User.TwoFactor.Status props
PropTypeDefault
nameRequired
"off" | "on" | "unavailable"—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/status.tsx

User.TwoFactor.Availablegate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Props

1 documented props

User.TwoFactor.Available props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/available.tsx

User.TwoFactor.Rootscope

The wizard around the steps: the fields it collects, and how far it has got. They live here rather than in a form because setting an authenticator up spans several screens — confirm the password, scan the code, put the recovery codes away — and each one is a gate that mounts and unmounts.

Props

3 documented props

User.TwoFactor.Root props
PropTypeDefault
config

The auth config, a slug from the registry (services/auth/<config>.config.ts). The nearest root's, else "default", when left out.

string—
enabled

Left out or true: required, and missing auth throws. "optional": renders its children and every part inside degrades without auth. false: off. A capability name such as "methods: password": requires auth with it.

Enabled—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/root.tsx

User.TwoFactor.Stepgate

Step takes name, one step or a list. Status gates on name="off" | "on" | "unavailable" whatever step the wizard is in, for the summary beside the buttons. Dialog maps the wizard's open state onto a host dialog through render={<Dialog />}; once the code is verified it cannot be dismissed until the recovery codes are acknowledged. Available is a gate for a settings entry that should exist only where two-factor does.

Props

3 documented props

User.TwoFactor.Step props
PropTypeDefault
nameRequired
TwoFactorStep | readonly TwoFactorStep[]—
children

Content rendered inside this part.

ReactNode—
render
ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/step.tsx

User.TwoFactor.Formaction · <form>

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Props

2 documented props · Native <form> attributes supported

User.TwoFactor.Form props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/form.tsx

User.TwoFactor.Disabledgate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Props

1 documented props

User.TwoFactor.Disabled props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/disabled.tsx

User.TwoFactor.Setupgate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Props

1 documented props

User.TwoFactor.Setup props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/setup.tsx

User.TwoFactor.Enabledgate

Disabled, Setup and Enabled read the person and the setup, so they work outside a Root: off with no setup, off with a setup in progress, on.

Props

1 documented props

User.TwoFactor.Enabled props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/enabled.tsx

User.TwoFactor.Labelcontent · <label>

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Required scope

User.TwoFactor.Label field=

Props

3 documented props · Native <label> attributes supported

User.TwoFactor.Label props
PropTypeDefault
fieldRequired
TwoFactorField—
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/label.tsx

User.TwoFactor.Passwordinput · <input>

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Required scope

User.TwoFactor.Password must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <input> attributes supported

User.TwoFactor.Password props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { invalid: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/password.tsx

User.TwoFactor.Startaction · <button>

Form holds the fields and prevents the page submit; inside a Root it uses the wizard's fields. Label takes field="account" | "password" | "new-password" | "code" | "codes-stored". Account is a read-only username input, so a password manager knows whose password this is. Password is the current password. Begin asks for setup and moves to confirm; Confirm submits the password for whatever was asked; Cancel leaves the wizard and forgets the secret shown; UseAnotherAccount signs out from the confirm step. Start runs setup straight from a password, without the wizard.

Required scope

User.TwoFactor.Start must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Start props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/start.tsx

User.TwoFactor.Qrcontent · <svg>

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Props

2 documented props · Native <svg> attributes supported

User.TwoFactor.Qr props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/qr.tsx

User.TwoFactor.Secretcontent

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Props

2 documented props

User.TwoFactor.Secret props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/secret.tsx

User.TwoFactor.Codeinput · <input>

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Required scope

User.TwoFactor.Code must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <input> attributes supported

User.TwoFactor.Code props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { invalid: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/code.tsx

User.TwoFactor.Enableaction · <button>

Qr is an <svg> drawn locally as one currentColor path. Secret is the key for manual entry. OpenInApp is an otpauth: link, for a phone that has no second screen to scan. Code is the first code from the app, cleared after every try. Enable is the form's one submit.

Required scope

User.TwoFactor.Enable must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Enable props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/enable.tsx

User.TwoFactor.BackupCodecontent · <li>

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Capability: backup-codes

Props

2 documented props · Native <li> attributes supported

User.TwoFactor.BackupCode props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { code: string }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/backup-code.tsx

User.TwoFactor.Regenerateaction · <button>

A new set of backup codes. Each old one stops working, which is the point: an account that has spent its way through the set it was given at setup has nothing left to use.

Capability: backup-codes

Required scope

User.TwoFactor.Regenerate must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Regenerate props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/regenerate.tsx

User.TwoFactor.Disableaction · <button>

From the on state. Replace, Remove and NewCodes each pass through confirm. Disable turns two-factor off from a password outside the wizard. Error is <div role="alert"> with { code, message }, the last failure of starting, enabling, disabling, new codes or adding a password.

Required scope

User.TwoFactor.Disable must be inside <User.TwoFactor.Form>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Disable props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/disable.tsx

User.TwoFactor.Errorcontent · <div>

From the on state. Replace, Remove and NewCodes each pass through confirm. Disable turns two-factor off from a password outside the wizard. Error is <div role="alert"> with { code, message }, the last failure of starting, enabling, disabling, new codes or adding a password.

Props

2 documented props · Native <div> attributes supported

User.TwoFactor.Error props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { code: AuthErrorCode; message: string }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/error.tsx

User.TwoFactor.Beginaction · <button>

Starts setting an authenticator up, which asks for the password first.

Required scope

User.TwoFactor.Begin must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Begin props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/begin.tsx

User.TwoFactor.Accountinput · <input>

The address the authenticator will be set up for, read-only. Shown because a password prompt with no account beside it is a question about which account, and `autocomplete=username` is what lets a password manager offer the right password for the field underneath.

Required scope

User.TwoFactor.Account must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <input> attributes supported

User.TwoFactor.Account props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/account.tsx

User.TwoFactor.UseAnotherAccountaction · <button>

Signs out from the confirm step. Somebody who does not know this account's password is usually somebody looking at the wrong account, and the only way on from there is out.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.UseAnotherAccount props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/use-another-account.tsx

User.TwoFactor.Confirmaction · <button>

The password, for whatever the wizard was asked for. One part rather than four, because from the person's side this is one act: proving the account is theirs before anything about how they sign in changes.

Required scope

User.TwoFactor.Confirm must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Confirm props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/confirm.tsx

User.TwoFactor.Cancelaction · <button>

Leaves the wizard, forgetting the secret this browser was shown.

Required scope

User.TwoFactor.Cancel must be inside <User.TwoFactor.Root>.

User.TwoFactor.Cancel requires User.TwoFactor.Root.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Cancel props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/cancel.tsx

User.TwoFactor.OpenInAppcontent · <a>

The `otpauth:` link the authenticator app registered itself for. On a phone there is no second screen to point a camera at, so scanning is impossible and the link is the only way in short of typing the secret.

Props

2 documented props · Native <a> attributes supported

User.TwoFactor.OpenInApp props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/open-in-app.tsx

User.TwoFactor.ShowCodesaction · <button>

Leaves the success step for the recovery codes, which are shown once. Where a backend hands out none it is the way out of the wizard instead: the step after this one would be empty, and there would be nothing to press on it.

Capability: backup-codes

Required scope

User.TwoFactor.ShowCodes must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.ShowCodes props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/show-codes.tsx

User.TwoFactor.CopyCodesaction · <button>

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Capability: backup-codes

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.CopyCodes props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { copied: boolean; failed: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/copy-codes.tsx

User.TwoFactor.DownloadCodescontent · <a>

The codes as a file. A `data:` URL rather than a blob one, because a blob URL has to be revoked and an anchor nobody clicks would leak it for the life of the page.

Capability: backup-codes

Props

2 documented props · Native <a> attributes supported

User.TwoFactor.DownloadCodes props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/download-codes.tsx

User.TwoFactor.PrintCodesaction · <button>

Paper, which is the one place a recovery code survives losing the phone and the laptop.

Capability: backup-codes

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.PrintCodes props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/print-codes.tsx

User.TwoFactor.CodesStoredinput · <input>

Says the codes are somewhere safe, which is what finishes setup.

Capability: backup-codes

Required scope

User.TwoFactor.CodesStored must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <input> attributes supported

User.TwoFactor.CodesStored props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { checked: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/codes-stored.tsx

User.TwoFactor.Finishaction · <button>

ShowCodes moves from the success step to the codes. BackupCode is an <li> repeated once per code, with { code }, while the setup that brought them is on screen. CopyCodes gets { copied, failed }; DownloadCodes is an <a download> with a data: URL; PrintCodes prints them. CodesStored is the checkbox that says they are kept somewhere safe, and Finish waits for it, since the codes are shown once. Regenerate asks for a new set, and every old code stops working.

Capability: backup-codes

Required scope

User.TwoFactor.Finish must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Finish props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/finish.tsx

User.TwoFactor.Replaceaction · <button>

Swaps the authenticator for another one, by turning this one off and setting one up again: Better Auth keeps one TOTP per account and refuses a second with TOTP_ALREADY_ENABLED, so there is no one-step replacement to offer.

Required scope

User.TwoFactor.Replace must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Replace props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/replace.tsx

User.TwoFactor.Removeaction · <button>

Asks to turn the authenticator off, which is confirmed with the password.

Required scope

User.TwoFactor.Remove must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.Remove props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/remove.tsx

User.TwoFactor.NewCodesaction · <button>

Asks for a new set of recovery codes. Each code is spent when it is used, so an account that has worked its way through the set it was given has no way back in left.

Required scope

User.TwoFactor.NewCodes must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.NewCodes props
PropTypeDefault
children

Content rendered inside this part.

ReactNode—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/new-codes.tsx

User.TwoFactor.Providercontent

Who this account signs in through, so the explanation can name them rather than say "a provider". The ids come out as the backend spells them, `google` and `github`, and whoever writes the sentence gives them their capital letters.

Capability: methods: password

Props

2 documented props

User.TwoFactor.Provider props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { provider: string | null; providers: readonly string[] }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/provider.tsx

User.TwoFactor.NewPasswordinput · <input>

The password an account that arrived through a provider is being given.

Capability: methods: password

Required scope

User.TwoFactor.NewPassword must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <input> attributes supported

User.TwoFactor.NewPassword props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { invalid: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/new-password.tsx

User.TwoFactor.AddPasswordaction · <button>

Gives the account a password, which is what makes a second factor possible for it: the code is asked for on a password sign-in, and there was no password sign-in before this.

Capability: methods: password

Required scope

User.TwoFactor.AddPassword must be inside <User.TwoFactor.Root>.

Props

2 documented props · Native <button> attributes supported

User.TwoFactor.AddPassword props
PropTypeDefault
children

Content rendered inside this part.

ReactNode | ((state: { pending: boolean }) => ReactNode)—
render

Replace the default element with your own while retaining the part’s behavior and accessibility.

ReactElement—

Required props are marked. A dash means no explicit default is declared in this component.

Source: @slices.sh/auth/src/headless/two-factor/add-password.tsx

User.Rootscope

Says which auth the User parts below read, and whether they may do without it. The parts need no root: left out, they read the nearest auth root's config, or "default", and render nothing without auth. `<User.Root enabled="optional">` is for the one who wants that said in the code.

Props

3 documented props

User.Root props
PropTypeDefault
config

The auth config, a slug from the registry (services/auth/<config>.config.ts). The nearest root's, else "default", when left out.

string—
enabled

Left out or true: required, and missing auth throws. "optional": renders its children and every part inside degrades without auth. false: off. A capability name such as "methods: password": requires auth with it.

Enabled—
children

Content rendered inside this part.

ReactNode—

Required props are marked. A dash means no explicit default is declared in this component.

Alternate public imports
  • User.Root from @slices.sh/auth/headless/user
  • User.Root from @slices.sh/auth/headless/passkeys
  • User.Root from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/root.tsx

When something is missing

No service configuration
Install the auth setup below and import "@/services". Required service roots report missing setup; gates do not replace that configuration.
Missing capability or parent scope
Follow each part’s context requirements and the example composition. Capability-aware controls hide when the provider cannot perform their action.
Read the setup guide →