Two-Factor Code Field
Six-digit OTP field for Cal auth and security flows.
Reusable six-digit TOTP input used across sign-in, enable/disable 2FA, and admin gates. Built on coss OTPField with Field label, description, and error wiring.
import { TwoFactorCodeField } from "@components/auth/two-factor-code-field";Ownership
App — not @coss/ui/shared. Lives under apps/web/components/auth because it is auth-flow UI: i18n for per-slot aria labels (otp_character_position), fixed length/numeric validation for Cal 2FA, and call sites that are web-only (login, settings modals, admin data gate). Reuse the App import; do not relocate for docs.
Example
Use the App component directly. The production settings flow clears server errors while the value changes and submits as soon as all six digits are present:
<TwoFactorCodeField
id="enable-2fa-totp-code"
name="totpCode"
label={t("2fa_code")}
description={t("2fa_enabled_instructions")}
errorMessage={errorMessage}
errorTestId="error-submitting-code"
autoFocus
value={totpCode}
onValueChange={(value) => {
if (errorMessage) setErrorMessage(null);
setTotpCode(value);
}}
onValueComplete={(value) => void handleTotpSubmit(undefined, value)}
/>This is the canonical composition from EnableTwoFactorModal. Keep OTP slot rendering, numeric validation, and localized accessibility labels inside TwoFactorCodeField; do not recreate them at the call site or in documentation.
API
| Prop | Type | Notes |
|---|---|---|
id | string | Root field id; first slot gets ${id}-input |
label | string | Visible label (or sr-only when hiddenLabel) |
value | string | Controlled OTP value |
onValueChange | (value: string) => void | Fires as digits change |
onValueComplete | (value: string) => void | Fires when all 6 digits are filled |
description | string | Optional helper under the slots |
errorMessage | string | null | Shows FieldError when set |
errorTestId | string | Test id on the error element |
hiddenLabel | boolean | Keeps label for a11y, visually hidden |
autoFocus | boolean | Focuses the first slot |
required | boolean | Passed to OTPField |
name | string | Form field name |
className | string | Field wrapper classes |
Also exports TWO_FACTOR_CODE_LENGTH (6).
Accessibility
- Root
OTPFieldhasaria-labelfromlabel,autoComplete="one-time-code",inputMode="numeric",validationType="numeric". - Slot 0 uses the field label; slots 1–5 use
otp_character_positionwith position/length. - Errors set
aria-invalidon each slot and renderFieldError.
Call sites
| File | Notes |
|---|---|
apps/web/components/auth/TwoFactor.tsx | Login / challenge wrapper (hidden form field + submit-on-complete) |
apps/web/components/settings/EnableTwoFactorModal.tsx | Enable 2FA TOTP step |
apps/web/components/settings/DisableTwoFactorModal.tsx | Disable 2FA confirmation code |
apps/web/modules/admin-dataview/components/AdminDataTwoFactorGate.tsx | Admin data studio 2FA gate |
Example URLs
Not exhaustive — a few places you can open locally (e.g. http://localhost:3000) to see this in product.
| Path | Notes |
|---|---|
/auth/login | 2FA challenge after password (mid-flow) |
/settings/security/two-factor-auth | Enable/disable 2FA — opens in dialog |