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.
| Surface | Key tokens / theme keys |
|---|---|
| AuthForm outer container | AuthForm theme key · theme.outer controls width constraint |
| AuthorizingForm centering | AuthorizingForm 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 chrome | Field, FieldLabel, FieldContent, Input, InputOTP, InputOTPSlot — same as Forms family |
| OTP slot active highlight | InputOTPSlot 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 style | text-muted-foreground line-through |
| Alert tones | Alert theme key · variant prop: warning, destructive, info, success, default |