Sign in

Panda visitor sign-in rendered inline (not a dialog): the same panels, API calls and session as the provider's auth dialog (email + password, Continue with Google, sign-up with phone + consent, forgot / reset password),…

Root + partsClient component@pandaidx/widgets 1.14.1

Anatomy

Every piece of Sign in as it nests in code. A root holds the data and state; its parts and hooks only work inside it.

<PandaProvider>Mounted once in app/layout.tsx
<PandaSignIn />complete widget

Panda visitor sign-in rendered inline (not a dialog): the same panels, API calls and session as the provider's auth dialog (email +…

<PandaAuthRoot>Holds the data and state its parts and hooks read
<AuthStep />

The step on screen with its tabs and Back link: the whole Panda flow in one part.

<AuthWhen />

Its children only on mode (a step or a list): your own heading, image or copy per step.

<AuthLogin />

Login form (email, password, Google, Forgot password → forgot step), always shown.

<AuthSignup />

Sign-up form with the required marketing consent (Google sign-up asks phone + consent first).

<AuthForgotPassword />

Email for the reset (link + 6-digit code), then the code step.

<AuthVerifyCode />

The 6-digit code of the email (15 minutes, 5 attempts), then the new password step.

<AuthResetPassword />

New password + confirm; signs the visitor in.

<AuthTabs />

Login / Sign Up switch in the design's tab style (on those steps).

<AuthGoogle />

Continue with Google on its own, anywhere in your layout (pair with design={{ providers: "none" }} so the login form…

<AuthDivider />

The "OR" line (design dividerLabel, align), anywhere in your layout.

<AuthBackLink />

Back to Login on the forgot and code steps.

<AuthLegal />

Privacy Policy and Terms of Service links of the site.

</PandaAuthRoot>
<PandaAuthButton />complete widget

Header account entry: signed out it opens the auth dialog (styled by the theme); signed in the avatar opens the account menu (collections,…

<PandaAuthCallback />complete widget

Google sign-in return page: render it at /auth/callback; it exchanges ?ott= for the visitor session and redirects.

</PandaProvider>

Installation

  1. 1
    npm install <tarball or @pandaidx/widgets>
    Terminal
    npm install https://github.com/pandaidx/panda-widgets/releases/download/v1.14.1/pandaidx-widgets-1.14.1.tgz
  2. 2
    import "@pandaidx/widgets/styles.css" in app/layout.tsx
  3. 3
    Wrap the site in <PandaProvider publicKey apiUrl preset theme> (app/layout.tsx)
  4. 4
    In each page (Server Component) call the widget's server loader, then render the widget with its data
  5. 5
    Allow the images: next.config images.remotePatterns [{ protocol: "https", hostname: "**" }] (listing and blog photos come from several CDNs)

Usage

PandaSignIn
// app/login/page.tsx
import { PandaSignIn } from "@pandaidx/widgets/client"
export default function LoginPage() {
return (
<main className="flex min-h-[70vh] items-center justify-center px-6 py-24">
<PandaSignIn title="Agent Login" modes={["login"]} redirectTo="/" />
</main>
)
}
// Editorial login section: underline fields with floating labels, Google on top, text-link switch, arrow submit
<PandaSignIn
title="Welcome back"
align="start"
tabsStyle="text-link"
providers="top"
providerStyle="outline"
dividerLabel="or with email"
fieldVariant="underline"
labelStyle="floating"
size="lg"
buttonVariant="link-arrow"
forgotPlacement="below-password"
legal="compact"
labels={{ signupTitle: "Create your account", loginButton: "Sign in", toSignupLink: "Join now" }}
classNames={{ root: "max-w-lg border-0 bg-transparent p-0" }}
/>
// components/login-dialog.tsx ("use client"): the site's own shadcn Dialog around the widget (no Panda trigger)
// <Dialog><DialogTrigger asChild><Button>Sign in</Button></DialogTrigger>
// <DialogContent className="sm:max-w-md">
// <DialogTitle className="sr-only">Sign in</DialogTitle>
// <PandaSignIn title="" size="sm" tabsStyle="underline" classNames={{ root: "border-0 p-0" }} />
// </DialogContent>
// </Dialog>

Examples

Custom login built from the headless hook

PandaSignIn
"use client"
import { usePandaAuthForm } from "@pandaidx/widgets/client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export const LoginCard = () => {
const auth = usePandaAuthForm({ redirectTo: "/account" })
if (auth.mode !== "login") return null // or render the other steps from auth.fields
return (
<form onSubmit={auth.submit} className="flex flex-col gap-4">
<h2>Welcome back</h2>
<Button type="button" variant="outline" onClick={auth.loginWithGoogle}>Continue with Google</Button>
<p className="text-center text-sm">or</p>
<Input type="email" placeholder="Email" {...auth.field("email")} />
{auth.errors.email ? <p role="alert">{auth.errors.email}</p> : null}
<Input type="password" placeholder="Password" {...auth.field("password")} />
{auth.errors.form ? <p role="alert">{auth.errors.form}</p> : null}
<Button type="submit" disabled={auth.pending}>Sign in</Button>
<Button type="button" variant="ghost" onClick={auth.forgotPassword}>Forgot password?</Button>
</form>
)
}

Rearranged sign-in (Google first, no tabs, compact legal)

PandaSignIn
<PandaSignIn title="Welcome back" modes={["login"]} parts={{ tabs: false, google: "top", divider: "or with email", forgot: "below-password", legal: "compact" }} />

Header account link of your own

PandaSignIn
"use client"
import { usePandaAuth } from "@pandaidx/widgets/client"
export const AccountLink = () => {
const { signedIn, user, openAuth, openDrawer } = usePandaAuth()
return signedIn ? (
<button type="button" onClick={() => openDrawer("collections")}>{user?.name ?? "My account"}</button>
) : (
<button type="button" onClick={() => openAuth("login")}>Sign in</button>
)
}

Sign-in as a page instead of the dialog

PandaSignIn
// app/layout.tsx: <PandaProvider … open={{ auth: { mode: "page", href: "/sign-in" } }}>
// app/sign-in/page.tsx
export default async function SignIn({ searchParams }: { searchParams: Promise<{ mode?: string; redirectTo?: string }> }) {
const { mode, redirectTo } = await searchParams
return <PandaSignIn defaultMode={mode === "signup" ? "signup" : "login"} signedIn="redirect" redirectTo={redirectTo ?? "/"} />
}

Login and sign-up side by side

PandaAuthRoot
<PandaAuthRoot redirectTo="/account" design={{ providers: "bottom", fieldVariant: "underline" }}>
<div className="grid gap-12 md:grid-cols-2">
<section>
<h2>Welcome back</h2>
<AuthWhen mode={["login", "signup"]}><AuthLogin /></AuthWhen>
<AuthWhen mode={["forgot-password", "verify-token", "reset-password"]}><AuthStep /></AuthWhen>
</section>
<section><h2>New here?</h2><AuthSignup /><AuthLegal /></section>
</div>
</PandaAuthRoot>

Stepper with your own heading per step

PandaAuthRoot
<PandaAuthRoot defaultMode="signup">
<AuthWhen mode="signup"><h1>Create your account</h1></AuthWhen>
<AuthWhen mode="login"><h1>Sign in</h1></AuthWhen>
<AuthWhen mode={["forgot-password", "verify-token", "reset-password"]}><h1>Reset your password</h1></AuthWhen>
<AuthStep />
<AuthLegal />
</PandaAuthRoot>

Google above the tabs

PandaAuthRoot
<PandaAuthRoot design={{ providers: "none", dividerLabel: "or" }}>
<AuthWhen mode={["login", "signup"]}>
<AuthGoogle />
<AuthDivider />
</AuthWhen>
<AuthStep />
<AuthLegal />
</PandaAuthRoot>

API reference

PandaSignIn

Panda visitor sign-in rendered inline (not a dialog): the same panels, API calls and session as the provider's auth dialog (email + password, Continue with Google, sign-up with phone + consent, forgot / reset password), styled by the theme (dialog, input and button tokens). Use it for a /login page or an "Agent Login" / login box; login on a Panda site is ALWAYS this widget or PandaAuthButton, never a hand-built password form or a link/form to the Panda dashboard. Signed in it shows a small account summary (My Account, Sign Out), nothing, or redirects. It has no trigger button: it is the block you place in a page, a section, or inside the site's own Dialog / Sheet (<DialogContent><PandaSignIn …/></DialogContent>). Rearrange and restyle it with params, never CSS on its markup: switch (tabs, tabsStyle, tabsAlign), Google (providers, providerStyle, divider, dividerLabel), fields (fieldVariant, labelStyle, size), submit (buttonVariant, buttonWidth, buttonAlign), forgotPlacement, align, legal, copy (labels) and classNames slots. Every flow (verify → reset, forgot, Google, sign-up consent) works in every arrangement.

Import from @pandaidx/widgets/client · renders inside PandaProvider

PropTypeDefaultDescription
defaultMode"login" | "signup""login"Panel shown first (falls back to the first of modes).
modes"login" | "signup"["login","signup"]Entry modes on offer; ["login"] hides Sign Up (an agents-only login box).
titlestring"Sign In"Heading above the panels (every mode unless labels sets a title per mode); an empty string hides it.
descriptionstring—Short copy under the heading.
redirectTostring—Page to open after signing in or signing up. Default: stay on the page and show the signed-in state.
signedIn"account" | "hidden" | "redirect""account"Already signed in: a small account summary (My Account, Sign Out), nothing, or go to redirectTo (else /).
tone"light" | "dark""light"light: the theme surface; dark: a dark card (theme ink) with light text for dark sections and dark dialogs.
tabsbooleantrueShow the Login / Sign Up switch (only with two modes). false = only defaultMode is reachable.
tabsStyle"segmented" | "underline" | "text-link""segmented"Switch look: segmented pill tabs, underline tabs with a bottom line, text-link no tabs but a "Don't have an account? Sign up" / "Already have an account? Log in" line under the form (placed by align).
tabsAlign"stretch" | "start" | "center" | "end""stretch"stretch tabs share the card width; start / center / end size them to their labels and place them.
providers"top" | "bottom" | "none""bottom"Continue with Google in the login panel: top above the email fields, bottom under the form, none email only (sign-up becomes the single email form). In sign-up the Google choice always comes after the phone + consent (Google provides neither).
providerStyle"outline" | "solid" | "ghost""outline"Google / "Create account with email" buttons: outline field-shaped box, solid filled with the theme button colors, ghost text button.
dividerbooleantrueThe line between the email form and Google.
dividerLabelstring"OR"Divider text; an empty string draws a plain line.
fieldVariant"outline" | "filled" | "underline"—Field look (PandaForm vocabulary): outline bordered box, filled tinted box without border, underline bottom line only (square, no side padding). Omitted = the theme's input design (DESIGN.md), else the native box. Retune it with --panda-auth-field-bg, --panda-auth-field-line, --panda-auth-field-focus, --panda-auth-field-radius, --panda-auth-field-height on classNames.root.
labelStyle"stacked" | "floating" | "hidden""hidden"hidden placeholders only (labels still announced to screen readers), stacked labels above the fields, floating small labels inside the field box.
size"sm" | "md" | "lg""md"Field, provider and submit height and type: sm 40px (compact dialogs), md 44px, lg 56px fields (hero login).
buttonVariant"solid" | "outline" | "ghost" | "link-arrow""solid"Submit look (PandaForm vocabulary): solid the theme button, outline, ghost text button, link-arrow text link with an arrow (always auto width). Radius, case and tracking come from the theme button tokens.
buttonWidth"full" | "auto""full"full stretches the submit across the card; auto sizes it to its label (placed by buttonAlign).
buttonAlign"start" | "center" | "end"—Placement of an auto / link-arrow submit. Default: align.
forgotPlacement"below-password" | "below-button" | "hidden""below-button""Forgot password?": below-password small link at the end under the password, below-button text button under the submit, hidden none (the reset flow is then unreachable from this box).
align"start" | "center""center"Alignment of the heading, the text-link switch, the divider label, the legal links and an auto submit. Fields always fill the width.
legal"show" | "compact" | "hidden""show"Privacy / Terms links row under the panels: show, compact (smaller, tighter line) or hidden. The sign-up consent checkbox (with its Terms / Privacy links) always stays: it is required.
labelsobject—Copy per instance (wins over the dictionary). Titles per mode replace title in the heading.
classNamesobject—Host classes per slot; plain Tailwind wins over the widget styles at every breakpoint (no !). Field colors, borders, radius and height set by fieldVariant / size / labelStyle follow the --panda-auth-field-* variables on root (e.g. [--panda-auth-field-bg:#f5f1ea]).
partsobject—Which parts render and where, in one object (wins over the flat props it covers). Each part reads the design kit by default: the title the dialog.title* tokens, the card dialog.padding / paddingMobile, the tabs the auth.tabs part (pill track), fields input.*, Google the field height and corners, the submit button.*. Example: { header: false, tabs: "underline", google: "top", divider: "or", forgot: "below-password", legal: "compact" }.

PandaAuthRoot

Root of a sign-in flow you lay out yourself: one part per step (login, sign-up, forgot password, 6-digit code, new password), the Panda handlers (session, Google, consent, reset by link or code) and the PandaSignIn panel design, no container. A two-column login + sign-up page, a stepper with your own heading per step, or your own surface around the Panda forms.

Import from @pandaidx/widgets/client · renders inside PandaProvider

PropTypeDefaultDescription
defaultMode"login" | "signup" | "forgot-password" | "verify-token" | "reset-password""login"Step on load.
modes"login" | "signup"["login","signup"]Entry modes of AuthTabs / AuthStep.
redirectTostring—Site path (/path) after login / sign-up / reset; default stays on the page.
designobject—The PandaSignIn design fields: tabs, tabsStyle, providers, providerStyle, divider, fieldVariant, labelStyle, size, buttonVariant, buttonWidth, buttonAlign, forgotPlacement, align, labels.
classNamesobject—Classes per panel part: root, tabs, tab, field, input, label, button, provider, divider, footer.
classNamestring—Class of the root element.
popupobject—The popup this flow opened in ({ id, snapshot }): sign-ups are attributed to it. PopupAuth sets it.
children*node—Your layout of the auth parts.

PandaAuthButton

Header account entry: signed out it opens the auth dialog (styled by the theme); signed in the avatar opens the account menu (collections, saved searches, likes, My Account, sign out). A header entry in your own markup (your icon, text, menu) is usePandaAuth() ({ user, signedIn, openAuth(), openDrawer(tab?), logout() }): never rebuild the dialog or the drawer.

Import from @pandaidx/widgets/client · renders inside PandaProvider

PropTypeDefaultDescription
variant"text" | "icon" | "avatar""text"Trigger: "Sign In / Sign Up" text (name beside the avatar once signed in), a user icon, or an avatar circle.
labelstring"Sign In / Sign Up"Signed-out text (text) or accessible name (icon, avatar).
classNamestring—Classes of the trigger (host typography/colors).
avatarClassNamestring—Classes of the avatar.
onActionfunction—Runs after the visitor picks an entry (e.g. close a mobile menu).
items"collections" | "saved" | "likes" | "account" | "dashboard" | "logout"["collections","saved","likes","account","logout"]Account menu entries in order (dashboard shows to the site owner only). For an inline menu or your own entries use PandaAccountMenu.
iconsbooleanfalseBuilt-in icon before each account menu entry.

PandaAuthCallback

Google sign-in return page: render it at /auth/callback; it exchanges ?ott= for the visitor session and redirects.

Import from @pandaidx/widgets/client · renders inside PandaProvider

PropTypeDefaultDescription
redirectTostring"/"Where to land after signing in.

Parts

<AuthStep />

The step on screen with its tabs and Back link: the whole Panda flow in one part.

<AuthWhen />

Its children only on mode (a step or a list): your own heading, image or copy per step.

<AuthLogin />

Login form (email, password, Google, Forgot password → forgot step), always shown.

<AuthSignup />

Sign-up form with the required marketing consent (Google sign-up asks phone + consent first).

<AuthForgotPassword />

Email for the reset (link + 6-digit code), then the code step.

<AuthVerifyCode />

The 6-digit code of the email (15 minutes, 5 attempts), then the new password step.

<AuthResetPassword />

New password + confirm; signs the visitor in.

<AuthTabs />

Login / Sign Up switch in the design's tab style (on those steps).

<AuthGoogle />

Continue with Google on its own, anywhere in your layout (pair with design={{ providers: "none" }} so the login form shows no second one).

<AuthDivider />

The "OR" line (design dividerLabel, align), anywhere in your layout.

<AuthBackLink />

Back to Login on the forgot and code steps.

<AuthLegal />

Privacy Policy and Terms of Service links of the site.

Hooks

usePandaAuth()inside PandaAuthRoot

{ user, signedIn, openAuth("login" | "signup"), openDrawer(tab?), logout() } — import from @pandaidx/widgets/client; your own sign-in links, gates and account menus (sign-in opens the site's way: PandaProvider open.auth)

usePandaAuthFlow()inside PandaAuthRoot

usePandaAuthFlow({ redirectTo? }) → { login({ email, password }), signup({ name, email, phone, password, marketingConsent: true }), loginWithGoogle(), signupWithGoogle({ phone, marketingConsent }), requestReset(email) (email with link + 6-digit code), verifyCode(email, code) → { ok, token }, resetPassword(token, password, confirmPassword), pending } — each resolves { ok, message, errors }; your own login / sign-up / reset forms over the Panda session. A sign-up of your own shows a consent checkbox (publish checks it) and the Privacy / Terms links

usePandaAuthForm()inside PandaAuthRoot

usePandaAuthForm({ defaultMode?, modes?, redirectTo?, initialValues?, initialToken? }) → { mode, setMode(mode), modes, fields (of the step), values, setValue(field, value), field(name) → props for your <input> (name, value, onChange, autoComplete, aria-invalid), errors ({ field: message, form: server message }), consent { checked, setChecked, copy, companyName, privacyLink, termsLink, error }, submit(event?) (runs the step: login, sign-up, send code → verify-token, verify → reset-password, new password), loginWithGoogle(), signupWithGoogle() (checks phone + consent first), forgotPassword(), backToLogin(), pending } — a sign-in fully of your own markup (any order: Google first, no tabs, your title), same session and rules as the dialog. Sign-up must render consent.copy beside a checkbox bound to consent.checked, with Terms / Privacy linked