Enable Two-Factor

Add two-factor authentication

Choose a second step to keep your account secure.

Prefer to do this later? Skip for now

About this block

Enable Two-FactorPRO

Full-height security setup step where the user picks a two-factor method from selectable cards (authenticator app, SMS, or security key), with a skip option. Perfect for guiding users through enabling 2FA.

Auth39: Enable Two-Factor

A full-height, centered security setup step that renders a rounded icon, heading, and description above a card holding a radio group of selectable two-factor method cards, each with an icon, title, and description, followed by a submit button and an optional skip prompt link.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/auth39?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/auth39?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/auth39.tsx, plus the button, radio-group, and field shadcn/ui primitives it uses for the submit action, the method selection group, and the form field layout.

Quick start

The installed file exports auth39Demo alongside the block: the exact props behind the preview above. Spread it to get a working 2FA method picker in one line.

tsx
import { Auth39, auth39Demo } from "@/components/beste/block/auth39";

export default function Page() {
  return <Auth39 {...auth39Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { KeyRound, ShieldCheck, Smartphone } from "lucide-react";
import { Auth39 } from "@/components/beste/block/auth39";

export default function Page() {
  return (
    <Auth39
      icon={<ShieldCheck className="size-6" />}
      heading="Add two-factor authentication"
      description="Choose a second step to keep your account secure."
      methods={[
        {
          value: "app",
          title: "Authenticator app",
          description: "Use a TOTP app like 1Password or Authy.",
          icon: <Smartphone className="size-5" />,
        },
        {
          value: "key",
          title: "Security key",
          description: "Use a hardware key or passkey.",
          icon: <KeyRound className="size-5" />,
        },
      ]}
      labels={{ submit: "Continue" }}
      skipPrompt={{ text: "Prefer to do this later?", linkLabel: "Skip for now", href: "/account" }}
    />
  );
}

Props

PropTypeDefaultDescription
iconReact.ReactNodeOptional glyph shown in the rounded badge above the heading
headingstringCentered page heading
descriptionstringCentered subtext below the heading
methodsTwoFactorMethod[][]Selectable method cards rendered inside the radio group
labels{ submit?: string }{}Submit button text; the button is omitted when unset
skipPrompt{ text: string; linkLabel: string; href: string }Optional skip line with a link, rendered below the card
classNamestringExtra classes for the outer <section>
ts
type TwoFactorMethod = {
  value: string;
  title: string;
  description: string;
  icon: React.ReactNode;
};

Behavior notes

  • The selected method is tracked by internal useState seeded from methods[0]?.value ?? "", so the first method is pre-selected and the selection is never lifted out or exposed via a callback.
  • Selection is keyed by method.value, so two methods sharing a value would both be highlighted as active.
  • The <form> calls e.preventDefault() on submit and does nothing else; there is no onSubmit prop, so wiring the actual 2FA enrollment is left to the integrator.
  • The submit Button is only rendered when labels.submit is a truthy string; otherwise the card shows just the method group with no action.
  • The leading icon badge, heading, description, and skipPrompt block each render only when their prop is provided, so the layout collapses gracefully when any is omitted.
  • The active method card is styled with border-primary bg-primary/5 while inactive cards get hover-only treatment; the visible RadioGroupItem control stays in sync with the whole-card <label> click target.
  • The skip link uses next/link; it navigates to skipPrompt.href and carries no skip callback, so it is a plain navigation rather than a dismiss handler.

Wiring the form up

This block ships the form markup only; state, validation, and submit are yours to add. Our guide wires the shadcn Field primitives to React Hook Form, TanStack Form, and Formisch on one field system.

More Auth blocks

View all Auth
PRO

auth35

Number Match Approval

Full-height push-approval screen that shows a large number to match in your authenticator app, with a live waiting indicator and a cancel option. Perfect for number-matching two-factor authentication.

PRO

auth11

Account Type Selection

Full-height onboarding step where the user picks an account type from selectable radio cards with icons and descriptions, then continues. Perfect for tailoring sign-up flows by persona or plan.

PRO

auth48

Security Question Setup

Full-height security question setup with a question select and an answer field, plus a skip option. Perfect for account recovery setup and identity verification fallbacks.

FREE

auth7

Account Chooser

Full-height account picker that lists saved accounts with avatars, names, and emails, plus a use-another-account row. Perfect for multi-account apps, SSO hubs, and returning-user sign-in.

PRO

auth22

Recovery Codes

Full-height two-factor backup screen that displays single-use recovery codes in a grid with copy and download actions, a warning callout, and a confirmation checkbox that gates the continue button. Perfect for 2FA setup and account recovery.

PRO

auth24

Complete Your Profile

Full-height profile setup step with an avatar uploader, a display name field, and a bio textarea, plus a skip option. Perfect for the post-sign-up onboarding step where users personalize their account.