UI.MD

Auth Dialog

An opinionated sign-in dialog with OAuth provider buttons, a passkey option, and a unified email continue flow.

Installation

$
npx shadcn@latest add https://ui.mattdowney.com/r/auth-dialog.json

Usage

import { AuthDialog } from "@/components/ui/auth-dialog"

Examples

Default

All four providers, passkey, and magic-link email in the unified continue flow. Handlers return promises, so the dialog runs the pending choreography and closes on resolve via controlled open. The footer slot carries the terms line.

Password Mode

emailMode="password" adds an inline password field and guarantees password in the continue payload. Also shows trimming the surface: one provider, no passkey.

Pending State

While a handler's promise is in flight, the clicked method shows a spinner (after a 200ms grace, so fast responses never flash) and every other method disables. Escape and the close button keep working.

Error Handling

A rejected promise un-pends the dialog and announces the rejection's message in the reserved error line under the header. The next attempt clears it.

API Reference

PropTypeDefaultDescription
providersAuthDialog("google" | "github" | "microsoft" | "apple")[]all fourWhich OAuth provider buttons to render, in array order. Pass [] to render none.
showPasskeyAuthDialogbooleantrueRenders the passkey button in the method stack.
showEmailAuthDialogbooleantrueRenders the email form (and the separator when methods render above it).
emailModeAuthDialog"magic-link" | "password""magic-link"Magic link collects email only. Password adds an inline password field and types password as required in the continue payload.
submitLabelAuthDialogReact.ReactNode"Send magic link" / "Continue"Overrides the email submit button's label. The default follows emailMode, because the two modes promise different things: in magic-link the button does not sign you in, it sends a mail and the next screen is your inbox, so it reads "Send magic link"; in password the submit really does sign you in, so it reads "Continue". Pass anything to override either, e.g. submitLabel="Continue" to keep the generic wording.
onProviderContinueAuthDialog(provider) => void | Promise<void>-Called with the clicked provider. Return a promise to get pending and error handling; return void for an immediate redirect.
onPasskeyContinueAuthDialog() => void | Promise<void>-Called when the passkey button is clicked. Same promise contract.
onEmailContinueAuthDialog({ email, password? }) => void | Promise<void>-Called on a valid email submit (native validation gates it). The payload is discriminated by emailMode.
titleAuthDialogReactNode"Sign in"Dialog title.
descriptionAuthDialogReactNode"Choose how you want to continue"Dialog description under the title.
triggerAuthDialogReactNode-Rendered through DialogTrigger. Omit when driving open externally.
footerAuthDialogReactNode-Terms/privacy copy pinned at the bottom in small muted text.
openAuthDialogboolean-Controlled open state, forwarded to the underlying Dialog. Pair with onOpenChange.
onOpenChangeAuthDialog(open: boolean) => void-Called when the open state changes.
defaultOpenAuthDialogbooleanfalseUncontrolled initial open state.