Two-Factor
better-auth-ui/two-factorFreeComponent
Two-factor plugin: the post-password challenge for authenticator, emailed, and backup codes, plus the settings card for enrollment and backup-code management.
Live preview
live · rendered by the registry
Rendered by better-auth-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://better-auth-ui.com/r/two-factor.jsonSource
1"use client"23import { REGEXP_ONLY_DIGITS } from "input-otp"4import { useId } from "react"56import { Field, FieldError, FieldLabel } from "@/components/ui/field"7import {8 InputOTP,9 InputOTPGroup,10 InputOTPSlot11} from "@/components/ui/input-otp"12import { cn } from "@/lib/utils"1314export type OtpFieldProps = {15 /** Visible label rendered above the slots. */16 label: string17 /** Number of slots — keep in sync with the server's code length. */18 length: number19 value: string20 onChange: (value: string) => void21 onComplete?: (value: string) => void22 autoFocus?: boolean23 className?: string24 errorMessage?: string25 disabled?: boolean26 name?: string27}2829/** Strip everything the numeric slots can't hold — pasted codes often carry spaces or dashes. */30function normalizeCode(value: string) {31 return value.replace(/\D/g, "")32}3334/**35 * Labelled one-time-code input.36 *37 * Shared by every code-based flow (email OTP, two-factor challenge,38 * two-factor enrollment) so slot sizing, pasting, and error wiring behave the39 * same everywhere.40 *41 * @param label - Visible label, also used as the accessible name.42 * @param length - Number of code characters.43 * @param errorMessage - Rendered below the slots when set.44 */45export function OtpField({46 autoFocus,47 className,48 disabled,49 errorMessage,50 label,51 length,52 name,53 onChange,54 onComplete,55 value56}: OtpFieldProps) {57 const inputId = useId()5859 return (60 <Field className={cn(className)} data-invalid={!!errorMessage}>61 <FieldLabel htmlFor={inputId}>{label}</FieldLabel>6263 <InputOTP64 aria-invalid={!!errorMessage}65 aria-label={label}66 autoComplete="one-time-code"67 autoFocus={autoFocus}68 containerClassName="w-full justify-center"69 disabled={disabled}70 id={inputId}71 inputMode="numeric"72 maxLength={length}73 name={name}74 pasteTransformer={normalizeCode}75 pattern={REGEXP_ONLY_DIGITS}76 value={value}77 onChange={(next) => onChange(normalizeCode(next))}78 onComplete={(completedCode) =>79 onComplete?.(normalizeCode(completedCode))80 }81 >82 <InputOTPGroup>83 {Array.from({ length }, (_, slotIndex) => (84 <InputOTPSlot85 index={slotIndex}86 key={`otp-slot-${String(slotIndex + 1)}`}87 />88 ))}89 </InputOTPGroup>90 </InputOTP>9192 <FieldError>{errorMessage}</FieldError>93 </Field>94 )95}
What it pulls in
npm packages
Other registry items
Files it writes
More from better-auth-ui
All 49 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Account Settingsaccount-settings | better-auth-ui | Components | Free | 2 deps | |
| Active Sessionsactive-sessions | better-auth-ui | Components | Free | 5 deps · 2 files | |
| Additional Fieldadditional-field | better-auth-ui | Components | Free | 5 deps | |
| Adminadmin | better-auth-ui | Components | Free | 4 deps · 3 files | |
| Anonymousanonymous | better-auth-ui | Components | Free | 4 deps · 3 files | |
| API Keyapi-key | better-auth-ui | Components | Free | 4 deps · 10 files | |
| Authauth | better-auth-ui | Components | Free | 2 deps | |
| Auth Providerauth-provider | better-auth-ui | Components | Free | 4 deps · 2 files |
