Skip to content

Auth & MFA Views

Five end-user-facing flows sharing one of two card recipes. AuthForm (theme key: AuthForm) frames a card below a page-level PageTitle, capped at max-w-3xl on the outer column and 35rem on the card itself. AuthorizingForm (theme key: AuthorizingForm) centers its card both horizontally and vertically, with no PageTitle: the card is the whole screen.

Every flow on this page renders live through the AuthDemo harness, which mounts the real view in its own sub-app with an isolated user store and an in-memory router. Submitting exercises the real loading, validation, toast, and redirect paths. Neither card renders PageTitle itself, so the demos show the card alone; a real shell supplies the page title above it.

Two sections carry a clearly-labeled design proposal card alongside the live demo. Those are hand-authored and are not renderings of the default theme; each names the backlog entry that tracks it.

Token surface: --background, --border, --muted-foreground, --ring, --destructive, --vueda-font-mono.

Chrome anatomy

The two cards below are structural diagrams, not styled specimens: they label the regions and name the theme slot that owns each one. For the actual rendered chrome, read the live demos in the sections that follow, which are the source of truth for padding, fill, radius, and type.

SignIn

ViewSignIn is the default sign-in view: an email and password form in an AuthorizingForm card. Post-login routing and MFA pending-flow detection come from AuthorizingForm (via useSignInFlow); submission, loading, and server-side validation mapping come from the inner ActionForm. Unlike the cards above, these demos render the live component through the AuthDemo harness, so submitting exercises the real loading, toast, and error paths. The first demo hosts a single Sonner; because the toast store is global, every demo on this page surfaces its toasts through that one corner overlay, as in a real app.

ChangePassword

ViewChangePassword puts three password fields in an AuthForm card: current password, new password, and confirmation. The fields are FormField rows wrapping WidgetTextInput, not hand-placed Field primitives, so they pick up the same validation wiring as any model form. The heading and subtitle come from AuthForm's header and subTitle props, which the layout supplies. A page-level PageTitle sits above the card in a real shell (see Chrome anatomy); the card below is what the view itself renders.

Submission, loading, per-field error mapping, and the form-scope validation summary all come from the inner ActionForm. Both demos render the live view through the AuthDemo harness, so submitting exercises the real paths.

TwoFactorAuth

ViewTwoFactorAuth runs in an AuthorizingForm card: the user holds a session token but the server demands a second factor. The view fetches the account's verified methods on mount, renders a method field, and reveals the OTP grid only once a method is chosen. For sms and email it adds a send-code button with a 60-second cooldown; a recovery-code toggle swaps the OTP grid for a single mono text input.

All of that is one interactive component, so the demo below is one live mount rather than a set of frozen states. Pick a method, send a code, watch the cooldown chip count down, and toggle into the recovery path.

Design proposal: segmented method picker

Not shipped. The card below is a proposal, not a rendering of the default theme: it replaces the method dropdown with a segmented rail of option cards carrying a per-option icon and a sub-line (the authenticator app, the masked phone, the email address). It removes a click and reads as the primary decision on the screen instead of a form row.

It is blocked on one primitive-level choice: a new WidgetSegmentedRadio, or a variant="rail" mode on WidgetRadioGroup. WidgetRadioGroup cannot express the rail recipe through theme keys alone, because per-option icons, sub-lines, and the group's selected-option chrome need structural template changes plus accessibility plumbing. ViewSetupDevice needs the same control, so the choice is taken once and applied to both.

SetupDevice

ViewSetupDevice is a three-step enrolment flow in an AuthForm card: choose a method, verify the device with a one-time code, then a confirmation panel. It takes required app and model props and reads its method choices from useModelConfig, so the demo seeds a small device model into the mounted store rather than passing the options in.

The view owns a visible step track (ViewSetupDevice.steps, step, stepNum, stepLabel, stepDivider) that advances as step moves through Choose, Verify, and Done. Completed steps swap their number for a check glyph. Note that the track is built from these dedicated slots and not from the Stepper family documented on Containers; reconciling the two is open Track F work, since the same visual pattern currently has two implementations.

Choosing email or sms reveals a destination field and sends a code on submit. Choosing the authenticator app instead returns a QR and a manual key, both rendered from the setup response.

RecoveryCodes

ViewRecoveryCodes sits in an AuthForm card and gates its whole body on whether the account has a TOTP device enrolled. With a device, it fetches the unused codes on mount and renders them as a numbered list with download, print, and copy-all controls, plus a regenerate submit. Without one, it renders a single warning telling the user to add a second factor first.

Regenerating is the form's action, so it runs through ActionForm like any other submit: the button shows the inline spinner while in flight, and the success handler swaps the list for the new codes and raises a toast.

Design proposal: mark redeemed codes

Not shipped. The card below is a proposal, not a rendering of the default theme. It keeps every code listed for transparency and strikes through the ones already redeemed, so the remaining count is readable at a glance instead of inferred.

It is blocked on the server: the recovery-codes endpoint returns unused codes only, so the client has nothing to mark. Once used codes are exposed, the realized shape is a data-used="true" variant on ViewRecoveryCodes.listItem rather than the hand-authored row below.

Customization surface

AuthForm and AuthorizingForm expose dedicated theme keys. All inner form content uses the same tokens and theme keys as the Forms family.

SurfaceKey tokens / theme keys
AuthForm outer containerAuthForm theme key · theme.outer controls width constraint
AuthorizingForm centeringAuthorizingForm theme key · theme.root controls full-viewport centering
Auth card background--card via bg-card
Auth card border--border via border-border
Auth card radius--radius-vueda-card via rounded-vueda-card
Field shell and input chromeField, FieldLabel, FieldContent, Input, InputOTP, InputOTPSlot — same as Forms family
OTP slot active highlightInputOTPSlot theme key · data-active attribute drives focus ring
Method picker selected card--primary via border-primary bg-primary/5
Step indicator active--primary via bg-primary border-primary text-primary-foreground
Step indicator done--success via bg-success; text-success-foreground for the checkmark
Step connector active-to-done--primary via bg-primary
Step connector pending--border via bg-border
Recovery code grid--muted, --border — same bordered content block as other panels
Used code styletext-muted-foreground line-through
Alert tonesAlert theme key · variant prop: warning, destructive, info, success, default

Documents matching: server v3.0.0a1.post1client v3.0.0-alpha.2