ElvixEmailCodeSignIn
A compact sign-in that starts at the email code, for an address you already have (a wallet payment, an invite). It sends the code once when it mounts and draws only where the code went, the six boxes, Send again and Use another email, inside your own card; onSignedIn hands you the session.
Your ticket is ready
Sign in to keep it in your account.
Sending a code to [email protected]…
Any six digits sign in; 000000 shows the wrong-code message.
"use client";import { ElvixEmailCodeSignIn } from "@elvix.is/sdk/react";// Inside <ElvixProvider clientId="your-app">, in your own card, right after// the step that told you the address (a wallet payment, an invite).export function TicketReady({ email, onSignedIn }: Props) {return (<ElvixEmailCodeSignInemail={email}title="Your ticket is ready" // optional, like the line belowdescription="Sign in to keep it in your account."deferOnboarding // the ticket first; username and passkey next timeonSignedIn={(session) => onSignedIn(session.token)} // verify it on your backendsignedIn={<p>Your ticket is in your account.</p>} // optional/>);}type Props = { email: string; onSignedIn: (token?: string) => void };
When to use it
Your app already knows the address: a person paid with Apple Pay or Google Pay while signed out and the wallet handed it over, or they followed an invite. Right after that step you want them signed in with one move, the code in their inbox, in a block short enough to sit under other content. <ElvixSignInForm> is the whole front door (every method, the header, the footer); this is the one step that is left.
Sign-in is sign-up, as everywhere in elvix: an address nobody signed in with yet creates the account.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
email | string | required | none | Where the code goes. Sent once when the block mounts. A new value starts over with its own code. An address that is not one opens on the email field instead. |
onSignedIn | (session: ElvixSignInResultOk) => void | optional | none | The person is signed in: { ok: true, method: "email_otp", token, redirect, deferred? }. The provider has already refreshed, so useElvixSession() is authenticated. Show the next thing here. Fires once. |
signedIn | ReactNode | optional | nothing | What the block shows once signed in, in place of everything else (its title and line too). |
title | ReactNode | optional | hidden | A heading above the block. A string renders as an <h3>; any element renders as given. Left out, nothing is drawn and no space is kept. |
description | ReactNode | optional | hidden | A line under the title, the same way (a string renders as a <p>). |
deferOnboarding | boolean | optional | false | Sign in at once when the app would offer a username or a passkey; session.deferred names the step put off ("username" or "passkey"), offered again at the next sign-in. Restoring a membership is never put off. |
autoFocus | boolean | optional | true | Focus the code boxes (or the email field) when they appear, so a phone shows its keyboard and the code suggestion. |
onResult | (r: ElvixSignInResult) => void | optional | none | As on <ElvixSignInForm>: the success above, and every failure with its code, { ok: false, error, message }. |
onAuthenticated | (r: { ok: true; redirect: string; token?: string }) => void | optional | none | The form's short success shape. Prefer onSignedIn. |
clientId | string | optional | <ElvixProvider clientId> | Your app's public clientId. |
mode | "interactive" | "preview" | optional | "interactive" | "preview" draws the block with email and demo state, and asks elvix nothing: no request, no email. |
previewStep | "code" | "email" | "username" | "passkey" | "recover" | optional | "code" | Preview only: which pane to draw. |
theme | "light" | "dark" | "auto" | optional | provider, then Console | Pins the palette; "auto" inherits the page. |
className | string | optional | none | On the block's root. It has no frame, padding or background of its own. |
classNames | { root?, header?, title?, description?, line?, code?, field?, button?, links?, link?, error? } | optional | none | More classes on each part: spacing, type, colour. The fields' fill, outline and radius are the variables below. |
What it does
- One code per address. The code goes out when the block mounts, and never again for a remount, React StrictMode's double effect or a reload while the code is still good (it is kept in
sessionStoragefor nine of its ten minutes). Send again is the only way to a second one, after the same cooldown as the form. - Use another email turns the block into an email field and Continue, in the same frame; the code goes to that address. Back returns to the code already sent. A wallet can hand over an address the person does not read, so this way out is always there.
- The code verifies at the sixth digit, typed, pasted or filled in by the phone's one-time-code suggestion. The boxes are one labelled group, described by the line that says where the code went, and that line and every error are announced.
- Onboarding happens in the block, with the sign-in form's own panes: pick a username, add a passkey, restore a membership.
deferOnboardingsigns in at once instead of offering the first two. - It never navigates.
onSignedInis where you do what comes next; the block then showssignedIn, or nothing. - DPoP works as in the form: with
dpopEnabledon, the session binds to this browser's key the moment it is created.
Errors
Every failure is shown in the block with its cause and what to do, and reported to onResult with its code.
error | Shown in the block | What the person does |
|---|---|---|
invalid_code | That code didn't work. Try again. | Types it again. |
expired | That code expired. Send a new one. | Send again. |
too_many_attempts | Too many wrong codes. Send a new one. | Send again. |
already_used | That code was already used. Send a new one. | Send again. |
too_recent | Wait Ns before requesting another code. | Send again counts down. |
too_many | Too many codes sent to this email. Try again in N. | Waits, or uses another email. |
network_error | Network hiccup. Try again. | Send again, or types the code again. |
user_banned, user_paused, gate_* | The same words as the sign-in form. | Contacts the app. |
Preview mode
To show the flow to someone who is not signing in (an organizer previewing what a buyer sees), draw it in preview mode. It hits no endpoint and sends no email.
// What the buyer will see, drawn for someone who is not buying: no request,// no email, the given address and a filled-in code.// Any pane: "code" (default), "email", "username", "passkey", "recover".
Look
No card, logo, method list or footer: your card is the frame, with its own padding; the block is one column with one gap, so a title or line left out leaves no space behind. Colours follow the provider and the Console like the form. The fields are a tint of the text colour with no outline, and a thin brand border when focused. Every part of that look is a variable, set on any ancestor; the fields are plain CSS, so no stylesheet order can override them. Words come from the provider's messages (signin.codeSentSubtitle, signin.sendAgain, signin.useAnotherEmail and the rest), like every SDK string.
/* On any ancestor of the block; every one is optional. */.ticket-card {--elvix-field: var(--muted); /* fill */--elvix-field-hover: var(--accent); /* fill under the pointer */--elvix-field-border: transparent; /* outline at rest */--elvix-field-focus: var(--ring); /* outline when focused */--elvix-field-focus-ring: none; /* a ring around a focused field */--elvix-field-radius: 12px;--elvix-field-text: var(--foreground);}
Mount it only for a signed-out person, and only with an address they gave you. The block signs in whoever reads that inbox: it is the email's owner who proves themselves with the code, never the person in front of your page. Requires @elvix.is/sdk 0.17 or later.
Related
ElvixSignInForm
The full branded sign-in surface (card, header, brand wash, every Console-enabled method, Secured-by-elvix badge). Exported as both `ElvixSignInForm` and the short alias `ElvixSignIn` — same component.
ReadElvixOnboarding
What happens after a redirect sign-in. Mount once; it opens itself on the step the server asked for (add a passkey, pick a username, restore a membership), finishes it in place and closes.
ReadElvixProvider
Top-level context. Fetches the brand chord, factor config, and copy from elvix; pipes session state and the event channel to every nested <Elvix*> component.
Read