Passwordless Code Sign In

sirius

Sign in without a password

We send a six-digit code that works once and expires in ten minutes. There is nothing to remember and nothing to leak.

or
Use your practice identity provider

No workspace yet? Create one

About this block

Passwordless Code Sign InPRO

A centered passwordless card that advances in place from a work email step with an SSO fallback to a monospace one-time-code entry with resend and go-back controls.

Auth51: Passwordless Code Sign In

A centered passwordless card that advances in place from a work email step with an SSO fallback to a monospace one-time-code entry with resend and go-back controls.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/auth51?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/auth51?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/auth51.tsx, the shadcn/ui input component it depends on, and the button21 component it uses for the actions.

Quick start

The installed file exports auth51Demo alongside the block: the exact props behind the preview above. Spread it to get a working sign-in page in one line.

tsx
import { Auth51, auth51Demo } from "@/components/beste/block/auth51";

export default function SignInPage() {
  return <Auth51 {...auth51Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Auth51 } from "@/components/beste/block/auth51";

export default function SignInPage() {
  return (
    <Auth51
      wordmark="sirius"
      emailStep={{
        heading: "Sign in without a password",
        description: "We send a six-digit code that works once and expires in ten minutes.",
        label: "Work email",
        placeholder: "[email protected]",
        submitLabel: "Send me a code",
        dividerLabel: "or",
        ssoButton: { label: "Use your practice identity provider", href: "/auth/sso" },
      }}
      codeStep={{
        heading: "Check your email",
        description: "It is valid for ten minutes and can only be used once.",
        length: 6,
        resendLabel: "Send it again",
        submitLabel: "Sign in",
        backLabel: "Use a different email",
      }}
      footer={{ label: "No workspace yet?", link: { label: "Create one", href: "/signup" } }}
    />
  );
}

Props

PropTypeDefaultDescription
wordmarkstring–Lowercase wordmark at the top of the card
emailStepEmailStep–Copy for the first step, including the SSO fallback
codeStepCodeStep–Copy for the second step and how many digits to render
footerFooterLink–Sign-up prompt under the card's hairline
classNamestring–Extra classes for the outer section
ts
type EmailStep = {
  heading: string;
  description: string;
  label: string;
  placeholder: string;
  submitLabel: string;
  dividerLabel: string;
  ssoButton: { label: string; href: string };
};

type CodeStep = {
  heading: string;
  description: string;
  length: number;
  resendLabel: string;
  submitLabel: string;
  backLabel: string;
};

type FooterLink = {
  label: string;
  link: { label: string; href: string };
};

Behavior notes

  • The two steps are one boolean of local state. The submit button on step one sets it to true with no validation and no network call, so the demo advances on any click. Wiring a real send means replacing that handler.
  • The back control sets the same boolean to false, so the card returns to the email step in place. Nothing is preserved between steps, since neither field is controlled.
  • The step is swapped by conditional render, not hidden with CSS, so only one step exists in the DOM at a time and the code inputs are freshly mounted each visit.
  • codeStep.length drives how many single-character inputs are rendered. Each gets maxLength={1} and an aria-label reading "Digit 1", "Digit 2" and so on, and only the first carries the generated id.
  • There is no auto-advance between digit boxes and no paste handling. Both are deliberate omissions, since they depend on your validation flow.
  • The digit inputs keep font-mono, which is the one place the set uses a monospace face on purpose: fixed-width digits stop the boxes from jittering as characters are typed.
  • The resend button has no handler at all. It is a real button so it is focusable and styled correctly, and the resend call is yours to add.

Wiring the form up

Both steps render their fields and leave submission to you. For validation, error states and submit handling, the patterns in React Hook Form, TanStack Form, and Formisch cover this shape of multi-step form.

More Auth blocks

View all Auth
PRO

auth53

Passwordless Sign-In Card

FREE

auth1

Centered Sign In Card

FREE

auth13

Centered Sign Up Card

Get Pro access