# Sign in

Authenticate with a password, a code, or a social account. Supply your own elements while the parts handle the flow.

Import: @slices.sh/auth/headless

Public parts: 55

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/auth@^0.1.10
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/auth.md

## Anatomy

### Composition

```text
SignIn.Root
  SignIn.Viewport
    SignIn.Step name="identify"
      SignIn.Providers providers={["google", "github"]}
        SignIn.OAuth provider="google"
          SignIn.LastUsed method="google"
        SignIn.OAuth provider="github"
      SignIn.MethodSeparator before={["google", "github"]}
      SignIn.Channels
        SignIn.ChannelList
          SignIn.ChannelTrigger channel="email"
          SignIn.ChannelTrigger channel="sms"
        SignIn.ChannelPanel channel="email"
          SignIn.IdentifierStage stage="identify"
            SignIn.Form
              SignIn.Label field="email"
              SignIn.Email
              SignIn.EmailSuggestion
              SignIn.Continue
            SignIn.PasskeyAutofill
            SignIn.Passkey
            SignIn.SignUpLink
          SignIn.IdentifierStage stage="method"
            SignIn.IdentifierPill
            SignIn.Password
            SignIn.RevealPassword
            SignIn.CapsLock
            SignIn.PasswordSubmit
            SignIn.SendCode
            SignIn.ForgotPassword
        SignIn.ChannelPanel channel="sms"
          SignIn.PhoneRoot
            SignIn.Phone
    SignIn.Step name="code"
      SignIn.ChallengeTitle
      SignIn.ChallengeChannel channel="email"
        SignIn.Destination
        SignIn.ChallengeInput
        SignIn.OpenInbox
      SignIn.CodeFeedback
        SignIn.CodeInput
      SignIn.AttemptsRemaining
      SignIn.VerifyCode
      SignIn.ResendCode
      SignIn.Restart
      SignIn.Verifying
        SignIn.Status
    SignIn.Step name="two-factor"
      SignIn.SecondFactor factor="authenticator"
        SignIn.TwoFactorCode
        SignIn.TrustDevice
        SignIn.VerifyTwoFactor
        SignIn.ChooseSecondFactor factor="backup-code"
      SignIn.SecondFactor factor="backup-code"
        SignIn.BackupCode
        SignIn.VerifyBackupCode
    SignIn.Step name="reset-request"
      SignIn.ResetMethod method="link"
        SignIn.RequestReset
        SignIn.ResetSent
      SignIn.BackToSignIn
    SignIn.Step name="reset-code"
      SignIn.ResetDestination
      SignIn.ResetCodeInput
      SignIn.ResendResetCode
    SignIn.Step name="reset"
      SignIn.ResetUnused
        SignIn.NewPassword
        SignIn.PasswordStrength
          SignIn.StrengthSegment
        SignIn.PasswordFeedback
        SignIn.PasswordRequirement
        SignIn.SubmitReset
      SignIn.ResetDone
    SignIn.Step name="locked"
      SignIn.Error
      SignIn.RetryAfter
    SignIn.Step name="expired"
    SignIn.Step name="signed-in"
```

## Google sign-in

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { SignIn, User } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <SignIn.Root>
      <User.SignedOut>
        <SignIn.OAuth provider="google">Continue with Google</SignIn.OAuth>
      </User.SignedOut>
      <User.SignedIn>
        <User.Image
          render={<img className="rounded-md" alt="Your avatar" />}
        />
        <p>
          Signed in as <User.Email />
        </p>
        <User.SignOut>Sign out</User.SignOut>
      </User.SignedIn>
    </SignIn.Root>
  )
}

```

Enable google in the auth methods below, then provision AuthLocker. OAuth and SignOut already render buttons.

## Email & password

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { SignIn, User } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <SignIn.Root emailPassword flow="all-methods">
      <User.SignedOut>
        <SignIn.Form>
          <SignIn.Label field="email">Email</SignIn.Label>
          <SignIn.Email />
          <SignIn.Label field="password">Password</SignIn.Label>
          <SignIn.Password />
          <SignIn.PasswordSubmit>Sign in</SignIn.PasswordSubmit>
          <SignIn.Error />
        </SignIn.Form>
      </User.SignedOut>
      <User.SignedIn>
        Welcome, <User.Name />.
      </User.SignedIn>
    </SignIn.Root>
  )
}

```

Enable password. Sign-up is a separate flow; this form signs in an existing account.

## Your own button

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { SignIn } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <SignIn.Root>
      <SignIn.OAuth
        provider="github"
        render={
          <button className="rounded-full bg-black px-5 py-3 text-white" />
        }
      >
        Continue with GitHub
      </SignIn.OAuth>
    </SignIn.Root>
  )
}

```

Enable github. render replaces the element while retaining handlers, disabled state and accessibility.

## Sign in with a passkey

```tsx
"use client"

// services/index.ts comes from https://slices.sh/r/services.json (or any provider setup)
import "@/services"
import { SignIn } from "@slices.sh/auth/headless/passkeys"

export function Example() {
  return (
    <SignIn.Root>
      <SignIn.Passkey>Use a passkey</SignIn.Passkey>
      <SignIn.Error />
    </SignIn.Root>
  )
}

```

Enable passkeys in the auth provider. This signs in an account that already registered a passkey.

## Complete API reference

### SignIn.IdentifierStage

gate

IdentifierStage gates on stage="identify" | "method" in the identifier-first flow and renders both in the other. Continue checks the address and moves on, focusing the field when it is not valid. IdentifierPill shows { email } and goes back to change it. SignUpLink is the host's registration link, and nothing without password support.

Import: @slices.sh/auth/headless

SignIn.IdentifierStage must be inside <SignIn.Root>.

```tsx
GateProps & { stage: "identify" | "method"; render?: ReactElement }
```

Alternate import: SignIn.IdentifierStage from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.IdentifierStage from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.IdentifierStage from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/identifier-stage.tsx

### SignIn.IdentifierPill

action · Element: button

IdentifierStage gates on stage="identify" | "method" in the identifier-first flow and renders both in the other. Continue checks the address and moves on, focusing the field when it is not valid. IdentifierPill shows { email } and goes back to change it. SignUpLink is the host's registration link, and nothing without password support.

Import: @slices.sh/auth/headless

SignIn.IdentifierPill must be inside <SignIn.Root>.

```tsx
PartProps<"button", { email: string }>
```

Alternate import: SignIn.IdentifierPill from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.IdentifierPill from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.IdentifierPill from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/identifier-pill.tsx

### SignIn.Continue

action · Element: button

IdentifierStage gates on stage="identify" | "method" in the identifier-first flow and renders both in the other. Continue checks the address and moves on, focusing the field when it is not valid. IdentifierPill shows { email } and goes back to change it. SignUpLink is the host's registration link, and nothing without password support.

Import: @slices.sh/auth/headless

SignIn.Continue must be inside <SignIn.Root>.

```tsx
PartProps<"button">
```

Alternate import: SignIn.Continue from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Continue from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Continue from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/continue.tsx

### SignIn.Viewport

content · Element: div

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

```tsx
PartProps<"div">
```

Alternate import: SignIn.Viewport from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Viewport from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Viewport from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/viewport.tsx

### SignIn.CodeFeedback

content · Element: div

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

```tsx
PartProps<"div", { invalid: boolean }>
```

Alternate import: SignIn.CodeFeedback from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.CodeFeedback from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.CodeFeedback from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/code-feedback.tsx

### SignIn.RetryAfter

content · Element: p

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

```tsx
PartProps<"p", { seconds: number }>
```

Alternate import: SignIn.RetryAfter from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.RetryAfter from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.RetryAfter from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/retry-after.tsx

### SignIn.LastUsed

content · Element: span

A label for a supported method that previously completed a login in this browser.

Import: @slices.sh/auth/headless

Capability: methods: oauth

```tsx
PartProps<"span"> & { method: OAuthProvider | "passkey" }
```

Alternate import: SignIn.LastUsed from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.LastUsed from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.LastUsed from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/last-used.tsx

### SignIn.SignUpLink

content · Element: a

The host owns the registration route; no link is offered without password support.

Import: @slices.sh/auth/headless

```tsx
PartProps<"a">
```

Alternate import: SignIn.SignUpLink from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.SignUpLink from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.SignUpLink from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/sign-up-link.tsx

### SignIn.CapsLock

content · Element: p

Password is <input type="password" autocomplete="current-password"> with { invalid }. RevealPassword toggles visibility with { visible }. CapsLock is <p role="status"> with { active }: keep it mounted and return empty content while inactive. PasswordStrength, StrengthSegment, PasswordFeedback and PasswordRequirement describe a new password, here the one set by a reset. PasswordSubmit is disabled until the address is valid and a password is typed.

Import: @slices.sh/auth/headless

Capability: methods: password

CapsLock must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"p", { active: boolean }>
```

Alternate import: CapsLock from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.CapsLock from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.CapsLock from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.CapsLock from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/caps-lock.tsx

### SignIn.ChallengeTitle

content · Element: h2

`inTerminal`: development with nothing set up to send codes; the code was printed where the app runs.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

ChallengeTitle needs SignIn.Root or SignUp.Root.

```tsx
PartProps<"h2", { multiple: boolean; channel: "email" | "sms"; inTerminal: boolean }>
```

Alternate import: SignIn.ChallengeTitle from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChallengeTitle from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChallengeTitle from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/challenge-title.tsx

### SignIn.Providers

content · Element: div

OAuth is <button type="button"> with { pending }, provider="google" | "github" and callbackURL; it renders nothing for a provider the backend lacks. Providers wraps them, sets data-layout to full or icons (layout="auto" switches to icons above three), and renders nothing when none is offered. LastUsed marks the method that last signed in from this browser. MethodSeparator renders only when a method before it and one after it are both offered.

Import: @slices.sh/auth/headless

Capability: methods: oauth

```tsx
PartProps<"div"> & {
  providers: readonly OAuthProvider[]
  layout?: ProviderLayout
}

export type ProviderLayout = "auto" | "full" | "icons"
```

Alternate import: SignIn.Providers from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Providers from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Providers from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/providers.tsx

### SignIn.MethodSeparator

content · Element: div

OAuth is <button type="button"> with { pending }, provider="google" | "github" and callbackURL; it renders nothing for a provider the backend lacks. Providers wraps them, sets data-layout to full or icons (layout="auto" switches to icons above three), and renders nothing when none is offered. LastUsed marks the method that last signed in from this browser. MethodSeparator renders only when a method before it and one after it are both offered.

Import: @slices.sh/auth/headless

Capability: methods: oauth

MethodSeparator needs SignIn.Root or SignUp.Root.

```tsx
PartProps<"div"> & {
  before: readonly Method[]
  after?: "passkey" | "channels"
}

type Method = OAuthProvider | "passkey"
```

Alternate import: SignIn.MethodSeparator from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.MethodSeparator from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.MethodSeparator from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/method-separator.tsx

### SignIn.ChallengeChannel

content

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

ChallengeChannel needs SignIn.Root or SignUp.Root.

```tsx
PartProps<null> & { channel: "email" | "sms" }
```

Alternate import: SignIn.ChallengeChannel from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChallengeChannel from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChallengeChannel from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/challenge-channel.tsx

### SignIn.ChallengeInput

input · Element: input

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

ChallengeInput needs SignIn.Root or SignUp.Root.

```tsx
PartProps<"input">
```

Alternate import: SignIn.ChallengeInput from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChallengeInput from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChallengeInput from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/challenge-input.tsx

### SignIn.Root

scope

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

```tsx
SignInRootProps & AuthRootProps

type SignInRootProps = RootProps & {
  onSignedIn?: (destination: string) => void
  channels?: readonly OtpChannel[]
  flow?: IdentifierFlow
  emailPassword?: boolean
}
```

Alternate import: SignIn.Root from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Root from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Root from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/root.tsx

### SignIn.ChannelPanel

content · Element: div

Base UI tabs between the email and SMS channels. Channels is the tabs root, ChannelList the tab list, ChannelTrigger channel="email" | "sms" one tab, ChannelPanel its panel. Unavailable channels render nothing, and with one channel the list disappears. Switching keeps what was typed in each.

Import: @slices.sh/auth/headless

SignIn.ChannelPanel must be inside a sign-in or sign-up Root.

```tsx
PartProps<"div"> & { channel: OtpChannel }
```

Alternate import: SignIn.ChannelPanel from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChannelPanel from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChannelPanel from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/channel-panel.tsx

### SignIn.ChannelTrigger

action · Element: button

Base UI tabs between the email and SMS channels. Channels is the tabs root, ChannelList the tab list, ChannelTrigger channel="email" | "sms" one tab, ChannelPanel its panel. Unavailable channels render nothing, and with one channel the list disappears. Switching keeps what was typed in each.

Import: @slices.sh/auth/headless

SignIn.ChannelTrigger must be inside a sign-in or sign-up Root.

```tsx
PartProps<"button"> & { channel: OtpChannel }
```

Alternate import: SignIn.ChannelTrigger from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChannelTrigger from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChannelTrigger from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/channel-trigger.tsx

### SignIn.ChannelList

content · Element: div

Base UI tabs between the email and SMS channels. Channels is the tabs root, ChannelList the tab list, ChannelTrigger channel="email" | "sms" one tab, ChannelPanel its panel. Unavailable channels render nothing, and with one channel the list disappears. Switching keeps what was typed in each.

Import: @slices.sh/auth/headless

SignIn.ChannelList must be inside a sign-in or sign-up Root.

```tsx
PartProps<"div">
```

Alternate import: SignIn.ChannelList from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ChannelList from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ChannelList from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/channel-list.tsx

### SignIn.Channels

content · Element: div

Base UI tabs between the email and SMS channels. Channels is the tabs root, ChannelList the tab list, ChannelTrigger channel="email" | "sms" one tab, ChannelPanel its panel. Unavailable channels render nothing, and with one channel the list disappears. Switching keeps what was typed in each.

Import: @slices.sh/auth/headless

SignIn.Channels must be inside a sign-in or sign-up Root.

```tsx
PartProps<"div">
```

Alternate import: SignIn.Channels from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Channels from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Channels from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/channels.tsx

### SignIn.Step

gate

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

```tsx
GateProps & { name: SignInStep; render?: ReactElement }

export type SignInStep =
  | "identify"
  | "reset-request"
  | "reset-code"
  | "reset"
  | "code"
  | "two-factor"
  | "locked"
  | "expired"
  | "signed-in"
```

Alternate import: SignIn.Step from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Step from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Step from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/step.tsx

### SignIn.Status

content · Element: p

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

Status must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"p", { pending: boolean; attempts: StatusAttempts | null }>

export type StatusAttempts = { remaining: number; limit: number }
```

Alternate import: SignIn.Status from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Status from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Status from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/status.tsx

### SignIn.Form

action · Element: form

Form prevents the page submit, holds one submitting part, and gives { pending } while any sign-in action runs. Label takes field="email" | "phone" | "password" | "code" | "two-factor-code" | "trust-device" and locks htmlFor. Email is <input type="email" autocomplete="email"> with { invalid }, and nothing without passwords or email codes. EmailSuggestion offers a corrected domain without submitting; OpenInbox links to the person's mail provider after an email challenge.

Import: @slices.sh/auth/headless

Form must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"form", { pending: boolean }>
```

Alternate import: SignIn.Form from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Form from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Form from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/form.tsx

### SignIn.Label

content · Element: label

Form prevents the page submit, holds one submitting part, and gives { pending } while any sign-in action runs. Label takes field="email" | "phone" | "password" | "code" | "two-factor-code" | "trust-device" and locks htmlFor. Email is <input type="email" autocomplete="email"> with { invalid }, and nothing without passwords or email codes. EmailSuggestion offers a corrected domain without submitting; OpenInbox links to the person's mail provider after an email challenge.

Import: @slices.sh/auth/headless

Label field=

```tsx
PartProps<"label"> & { field: LabelField }

export type LabelField = keyof typeof FIELDS
```

Alternate import: SignIn.Label from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Label from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Label from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/label.tsx

### SignIn.Email

input · Element: input

Form prevents the page submit, holds one submitting part, and gives { pending } while any sign-in action runs. Label takes field="email" | "phone" | "password" | "code" | "two-factor-code" | "trust-device" and locks htmlFor. Email is <input type="email" autocomplete="email"> with { invalid }, and nothing without passwords or email codes. EmailSuggestion offers a corrected domain without submitting; OpenInbox links to the person's mail provider after an email challenge.

Import: @slices.sh/auth/headless

Email must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: SignIn.Email from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Email from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Email from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/email.tsx

### SignIn.OpenInbox

content · Element: a

Form prevents the page submit, holds one submitting part, and gives { pending } while any sign-in action runs. Label takes field="email" | "phone" | "password" | "code" | "two-factor-code" | "trust-device" and locks htmlFor. Email is <input type="email" autocomplete="email"> with { invalid }, and nothing without passwords or email codes. EmailSuggestion offers a corrected domain without submitting; OpenInbox links to the person's mail provider after an email challenge.

Import: @slices.sh/auth/headless

OpenInbox requires SignIn.Root or SignUp.Root.

```tsx
PartProps<"a", { name: string }>
```

Alternate import: SignIn.OpenInbox from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.OpenInbox from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.OpenInbox from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/open-inbox.tsx

### SignIn.EmailSuggestion

action · Element: button

Form prevents the page submit, holds one submitting part, and gives { pending } while any sign-in action runs. Label takes field="email" | "phone" | "password" | "code" | "two-factor-code" | "trust-device" and locks htmlFor. Email is <input type="email" autocomplete="email"> with { invalid }, and nothing without passwords or email codes. EmailSuggestion offers a corrected domain without submitting; OpenInbox links to the person's mail provider after an email challenge.

Import: @slices.sh/auth/headless

EmailSuggestion requires SignIn.Root or SignUp.Root.

```tsx
PartProps<"button", { suggestion: string }>
```

Alternate import: SignIn.EmailSuggestion from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.EmailSuggestion from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.EmailSuggestion from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/email-suggestion.tsx

### SignIn.PhoneRoot

scope

The phone number and its country list, kept in the card's phone field as an E.164 number
or as nothing at all. Renders nothing itself, and nothing inside it when the backend sends
no codes by text.

The card's field keeps the id, so its label and the focus a refused send asks for find the
number's input without knowing a country list is there.

Import: @slices.sh/auth/headless

Capability: methods: phone-code

SignIn.PhoneRoot must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PhoneRootProps

type PhoneRootProps = RootProps & {
  /** Where a number typed without a `+` is read until a country is picked. ISO 3166-1 alpha-2. */
  defaultCountry?: string
  /** The countries a number may be in, which are also the ones the list offers. Without it, every country. */
  allowedCountries?: readonly string[]
  locale?: string
}
```

Alternate import: SignIn.PhoneRoot from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.PhoneRoot from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.PhoneRoot from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/phone-root.tsx

### SignIn.Phone

input · Element: input

The number, typed in its country's own format. It sits inside `SignIn.PhoneRoot`, which is
where the country list is too, and says "invalid" for the refusals the number's own
reading cannot know about.

Import: @slices.sh/auth/headless

Capability: methods: phone-code

Phone must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"input", { invalid: boolean; status: PhoneStatus }> & { invalidMessage?: string }
```

Alternate import: SignIn.Phone from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Phone from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Phone from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/sign-in-phone.tsx

### SignIn.Password

input · Element: input

Password is <input type="password" autocomplete="current-password"> with { invalid }. RevealPassword toggles visibility with { visible }. CapsLock is <p role="status"> with { active }: keep it mounted and return empty content while inactive. PasswordStrength, StrengthSegment, PasswordFeedback and PasswordRequirement describe a new password, here the one set by a reset. PasswordSubmit is disabled until the address is valid and a password is typed.

Import: @slices.sh/auth/headless

Capability: methods: password

SignIn.Password must be inside <SignIn.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: SignIn.Password from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Password from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Password from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/password.tsx

### SignIn.RevealPassword

action · Element: button

Shows or hides what was typed into the password field.

No `aria-pressed`: its own name changes, and a button announced as "Hide password,
pressed" says the same thing twice in opposite directions.

Import: @slices.sh/auth/headless

Capability: methods: password

SignIn.RevealPassword must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"button", { visible: boolean }>
```

Alternate import: Reveal from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.RevealPassword from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.RevealPassword from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.RevealPassword from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reveal-password.tsx

### SignIn.PasswordSubmit

action · Element: button

Password is <input type="password" autocomplete="current-password"> with { invalid }. RevealPassword toggles visibility with { visible }. CapsLock is <p role="status"> with { active }: keep it mounted and return empty content while inactive. PasswordStrength, StrengthSegment, PasswordFeedback and PasswordRequirement describe a new password, here the one set by a reset. PasswordSubmit is disabled until the address is valid and a password is typed.

Import: @slices.sh/auth/headless

Capability: methods: password

SignIn.PasswordSubmit must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: SignIn.PasswordSubmit from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.PasswordSubmit from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.PasswordSubmit from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/password-submit.tsx

### SignIn.OAuth

action · Element: button

OAuth is <button type="button"> with { pending }, provider="google" | "github" and callbackURL; it renders nothing for a provider the backend lacks. Providers wraps them, sets data-layout to full or icons (layout="auto" switches to icons above three), and renders nothing when none is offered. LastUsed marks the method that last signed in from this browser. MethodSeparator renders only when a method before it and one after it are both offered.

Import: @slices.sh/auth/headless

Capability: methods: oauth

OAuth must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
OAuthProps

type OAuthProps = PartProps<"button", { pending: boolean }> & {
  provider: OAuthProvider
  // Where the provider sends the person back; the backend's default when left out.
  callbackURL?: string
}
```

Alternate import: SignIn.OAuth from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.OAuth from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.OAuth from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/oauth.tsx

### SignIn.SendCode

action · Element: button

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

SendCode must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
SendCodeProps

type SendCodeProps = PartProps<"button", { pending: boolean; cooldown: boolean }> & { channel?: OtpChannel }
```

Alternate import: SignIn.SendCode from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.SendCode from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.SendCode from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/send-code.tsx

### SignIn.Destination

content

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

Destination must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<null>
```

Alternate import: SignIn.Destination from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Destination from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Destination from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/destination.tsx

### SignIn.CodeInput

input · Element: input

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

CodeInput must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: SignIn.CodeInput from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.CodeInput from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.CodeInput from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/code-input.tsx

### SignIn.VerifyCode

action · Element: button

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

VerifyCode must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: SignIn.VerifyCode from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.VerifyCode from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.VerifyCode from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/verify-code.tsx

### SignIn.Verifying

gate

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

Verifying must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
GateProps
```

Alternate import: SignIn.Verifying from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Verifying from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Verifying from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/verifying.tsx

### SignIn.ResendCode

action · Element: button

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

ResendCode must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"button", { seconds: number; pending: boolean; exhausted: boolean }>
```

Alternate import: SignIn.ResendCode from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResendCode from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResendCode from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/resend-code.tsx

### SignIn.AttemptsRemaining

content · Element: p

SendCode is a submit with { pending, cooldown } and channel="email" | "sms"; it refuses and focuses the field when the address is invalid, and renders nothing without that channel. The rest render only once a code was sent. Destination is the address it went to; ChallengeTitle gets { multiple, channel }; ChallengeChannel gates on the channel; ChallengeInput shows the address read-only. CodeInput is a numeric one-time-code input with maxLength locked, verifying once complete; VerifyCode is for a pasted code; Verifying stands where a spinner would be. ResendCode gets { seconds, pending }. AttemptsRemaining gets { remaining, limit } after the first wrong code. CodeFeedback gets { invalid } for a wrong code.

Import: @slices.sh/auth/headless

Capability: methods: email-code or phone-code

AttemptsRemaining must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"p", { remaining: number; limit: number }>
```

Alternate import: SignIn.AttemptsRemaining from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.AttemptsRemaining from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.AttemptsRemaining from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/attempts-remaining.tsx

### SignIn.Restart

action · Element: button

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

Restart must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"button">
```

Alternate import: SignIn.Restart from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Restart from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Restart from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/restart.tsx

### SignIn.Error

content · Element: div

Step gates on name and accepts render={<MotionPanel />} to animate; without render it is a wrapper-free gate. Status is the one polite live region, mounted for the card's life, that speaks waits and the attempt budget. Viewport is a plain container for animated steps. Error is <div role="alert"> with { code, message }, only for the current step's last failed action. RetryAfter gets { seconds } while a rate limit holds. Restart returns to identify and drops the challenge.

Import: @slices.sh/auth/headless

```tsx
PartProps<"div", { code: AuthErrorCode; message: string }>
```

Alternate import: SignIn.Error from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.Error from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.Error from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/error.tsx

### Authentication.Root

scope

Mode gates on name="sign-in" | "sign-up", so the hidden flow is unmounted, not hidden. Switch is a button with to="sign-in" | "sign-up", and renders nothing in the flow it points to.

Import: @slices.sh/auth/headless

```tsx
AuthenticationRootProps & AuthRootProps

type AuthenticationRootProps = RootProps & { defaultMode?: AuthMode; allowSignUp?: boolean }
```

Alternate import: Authentication.Root from @slices.sh/auth/headless/authentication

Source: @slices.sh/auth/src/headless/authentication/root.tsx

### Authentication.Mode

gate

Mode gates on name="sign-in" | "sign-up", so the hidden flow is unmounted, not hidden. Switch is a button with to="sign-in" | "sign-up", and renders nothing in the flow it points to.

Import: @slices.sh/auth/headless

Authentication.Mode must be inside <Authentication.Root>.

```tsx
GateProps & { name: AuthMode }
```

Alternate import: Authentication.Mode from @slices.sh/auth/headless/authentication

Source: @slices.sh/auth/src/headless/authentication/mode.tsx

### Authentication.Switch

action · Element: button

The way across: "Don't have an account? Sign up", and back again.

A button rather than a link, because both flows are this one page. An installed app that
keeps `/sign-in` and `/sign-up` as two routes links between them instead, and drops this.

Import: @slices.sh/auth/headless

Authentication.Switch must be inside <Authentication.Root>.

```tsx
PartProps<"button"> & { to: AuthMode }
```

Alternate import: Authentication.Switch from @slices.sh/auth/headless/authentication

Source: @slices.sh/auth/src/headless/authentication/switch.tsx

### Authentication.SignUpAvailable

gate

Keep the entire invitation out of the screen when password registration is unavailable.

Import: @slices.sh/auth/headless

Capability: methods: password

Authentication.SignUpAvailable must be inside <Authentication.Root>.

```tsx
GateProps
```

Alternate import: Authentication.SignUpAvailable from @slices.sh/auth/headless/authentication

Source: @slices.sh/auth/src/headless/authentication/sign-up-available.tsx

### User.SignedOut

gate

SignedIn and SignedOut open once the session has answered, so nobody flashes signed out. Loading covers the first load only. SessionExpired opens when the session on screen has run out while its name and picture are still shown. ReadError is <p role="alert"> with { message } when the session could not be read, and Retry reads it again.

Import: @slices.sh/auth/headless

```tsx
GateProps
```

Alternate import: User.SignedOut from @slices.sh/auth/headless/user

Alternate import: User.SignedOut from @slices.sh/auth/headless/passkeys

Alternate import: User.SignedOut from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/signed-out.tsx

### SignIn.Passkey

action · Element: button

Passkey is <button type="button"> with { pending }; it runs the ceremony on click and renders nothing where the backend or the browser cannot. PasskeyAutofill renders no element: it starts conditional mediation once, on mount, so the passkey is offered in the autofill list under the email field. A ceremony that ends without a credential is remembered, and Passkeys.SetupPrompt offers one after sign-in.

Import: @slices.sh/auth/headless/passkeys

Capability: methods: passkey

SignIn.Passkey must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/passkeys/sign-in/passkey.tsx

### SignIn.PasskeyAutofill

gate

Starts the passkey ceremony the browser runs from the identifier field itself.

Renders nothing: conditional mediation has no button, which is the point — the passkey is
offered in the autofill list under the email field, and the ceremony finishes as soon as
one is picked. It runs once, on mount, and only where the browser said it can, because
`startAuthentication` with autofill throws in a browser that cannot serve it.

The ceremony is owned here rather than by the pattern, which calls no hook of its own.

Import: @slices.sh/auth/headless/passkeys

Capability: methods: passkey

SignIn.PasskeyAutofill must be inside <SignIn.Root>.

```tsx
GateProps
```

Source: @slices.sh/auth/src/headless/passkeys/sign-in/passkey-autofill.tsx

### SignIn.SecondFactor

gate

One second factor or the other. The backup-code side renders nothing at all where the
backend cannot redeem one, so a lost authenticator is never offered a way out that this
deployment does not have.

Import: @slices.sh/auth/headless/two-factor

Capability: two-factor

SignIn.SecondFactor must be inside <SignIn.Root>.

```tsx
GateProps & { factor: SecondFactorKind }
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/second-factor.tsx

### SignIn.ChooseSecondFactor

action · Element: button

Switches to the other second factor, and is absent when there is no other to switch to.

Import: @slices.sh/auth/headless/two-factor

Capability: two-factor

SignIn.ChooseSecondFactor must be inside <SignIn.Root>.

```tsx
PartProps<"button"> & { factor: SecondFactorKind }
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/choose-second-factor.tsx

### SignIn.TwoFactorCode

input · Element: input

SecondFactor is a gate with factor="authenticator" | "backup-code": one is on screen at a time, so each gets its own form and Enter works. ChooseSecondFactor switches to the other factor and renders nothing when there is no other. TwoFactorCode is a numeric one-time-code input, read-only while it is checked, and takes focus when the step opens. TrustDevice is a checkbox, unticked again after Restart. VerifyTwoFactor submits, disabled until a code is typed.

Import: @slices.sh/auth/headless/two-factor

Capability: two-factor

SignIn.TwoFactorCode must be inside <SignIn.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/two-factor-code.tsx

### SignIn.BackupCode

input · Element: input

BackupCode is the input for one recovery code, with no one-time-code token so a password manager does not offer the last SMS. VerifyBackupCode submits it. The backup-code side of SecondFactor renders nothing where the backend cannot redeem one.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

SignIn.BackupCode must be inside <SignIn.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/backup-code.tsx

### SignIn.VerifyBackupCode

action · Element: button

BackupCode is the input for one recovery code, with no one-time-code token so a password manager does not offer the last SMS. VerifyBackupCode submits it. The backup-code side of SecondFactor renders nothing where the backend cannot redeem one.

Import: @slices.sh/auth/headless/two-factor

Capability: backup-codes

SignIn.VerifyBackupCode must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/verify-backup-code.tsx

### SignIn.TrustDevice

input · Element: input

SecondFactor is a gate with factor="authenticator" | "backup-code": one is on screen at a time, so each gets its own form and Enter works. ChooseSecondFactor switches to the other factor and renders nothing when there is no other. TwoFactorCode is a numeric one-time-code input, read-only while it is checked, and takes focus when the step opens. TrustDevice is a checkbox, unticked again after Restart. VerifyTwoFactor submits, disabled until a code is typed.

Import: @slices.sh/auth/headless/two-factor

Capability: two-factor

SignIn.TrustDevice must be inside <SignIn.Root>.

```tsx
PartProps<"input", { checked: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/trust-device.tsx

### SignIn.VerifyTwoFactor

action · Element: button

SecondFactor is a gate with factor="authenticator" | "backup-code": one is on screen at a time, so each gets its own form and Enter works. ChooseSecondFactor switches to the other factor and renders nothing when there is no other. TwoFactorCode is a numeric one-time-code input, read-only while it is checked, and takes focus when the step opens. TrustDevice is a checkbox, unticked again after Restart. VerifyTwoFactor submits, disabled until a code is typed.

Import: @slices.sh/auth/headless/two-factor

Capability: two-factor

SignIn.VerifyTwoFactor must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Source: @slices.sh/auth/src/headless/two-factor/sign-in/verify-two-factor.tsx

### SignIn.ForgotPassword

action · Element: button

Asks for the reset screen. Absent unless the backend can actually put a link in front of
somebody: a "Forgot password?" that leads nowhere is worse than none at all.

`stage="identify"` is for the link under the email field of an identifier-first card: it
is there only while that step is drawn. Where the email and the password stand together
there is no such step, and the password field's own link is the only one.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ForgotPassword must be inside <SignIn.Root>.

```tsx
PartProps<"button"> & { stage?: "identify" }
```

Alternate import: SignIn.ForgotPassword from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ForgotPassword from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ForgotPassword from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/forgot-password.tsx

### SignIn.ResetMethod

gate

How this install replaces a forgotten password: a link to the inbox, or a code to type.

A gate, because the two are different screens rather than one screen with a different
sentence — a link is followed away and comes back with a token, a code is typed here —
and only the backend knows which it can deliver.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetMethod must be inside <SignIn.Root>.

```tsx
GateProps & { method: "link" | "code" }
```

Alternate import: SignIn.ResetMethod from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetMethod from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetMethod from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-method.tsx

### SignIn.RequestReset

action · Element: button

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.RequestReset must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean; method: "link" | "code" }>
```

Alternate import: SignIn.RequestReset from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.RequestReset from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.RequestReset from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/request-reset.tsx

### SignIn.ResetSent

gate

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetSent must be inside <SignIn.Root>.

```tsx
GateProps<{ delivery: "mail" | "terminal" | "log" }>
```

Alternate import: SignIn.ResetSent from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetSent from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetSent from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-sent.tsx

### SignIn.BackToSignIn

action · Element: button

Leaves the reset screens. It drops the token as well as the intent, which is what takes
a live credential out of the address bar once it has been spent, and the code challenge
with it: coming back to a half-typed code for a reset nobody is doing any more is a
screen nobody asked for.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.BackToSignIn must be inside <SignIn.Root>.

```tsx
PartProps<"button">
```

Alternate import: SignIn.BackToSignIn from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.BackToSignIn from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.BackToSignIn from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/back-to-sign-in.tsx

### SignIn.ResetDestination

content

Where the reset code went, as the backend spells it back: often masked, never guessed.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetDestination must be inside <SignIn.Root>.

```tsx
PartProps<null>
```

Alternate import: SignIn.ResetDestination from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetDestination from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetDestination from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-destination.tsx

### SignIn.ResetCodeInput

input · Element: input

The code that proves the address a reset was asked for.

The same six digits as a sign-in code and a different thing entirely: a right one answers
a token that sets a password, and signs nobody in. Checked the moment its last digit
lands, so there is no button beside it.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetCodeInput must be inside <SignIn.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: SignIn.ResetCodeInput from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetCodeInput from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetCodeInput from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-code-input.tsx

### SignIn.ResendResetCode

action · Element: button

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResendResetCode must be inside <SignIn.Root>.

```tsx
PartProps<"button", { seconds: number; pending: boolean; exhausted: boolean }>
```

Alternate import: SignIn.ResendResetCode from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResendResetCode from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResendResetCode from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/resend-reset-code.tsx

### SignIn.ResetUnused

gate

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetUnused must be inside <SignIn.Root>.

```tsx
GateProps
```

Alternate import: SignIn.ResetUnused from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetUnused from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetUnused from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-unused.tsx

### SignIn.NewPassword

input · Element: input

The password being set by a reset, which is a new one and is announced as such.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.NewPassword must be inside <SignIn.Root>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: SignIn.NewPassword from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.NewPassword from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.NewPassword from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/new-password.tsx

### SignIn.PasswordStrength

content · Element: div

The meter over a new password: `data-level` is the score, 0 to 4, and absent until one is
known.

Absent over an empty field too. A meter that says "very weak" before anything has been
typed is a screen telling somebody off for not having started.

Import: @slices.sh/auth/headless

Capability: methods: password

PasswordStrength must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"div", { score: number | null; levels: number }>
```

Alternate import: Strength from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.PasswordStrength from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.PasswordStrength from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.PasswordStrength from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/password-strength.tsx

### SignIn.StrengthSegment

content · Element: span

One bar of the meter, repeated once per level.

The count comes from the estimator's own scale rather than from the card, so a skin
cannot draw five bars over a score that only goes to four.

Import: @slices.sh/auth/headless

Capability: methods: password

StrengthSegment must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
Props

type Props = PartProps<"span", { index: number; filled: boolean }>
```

Alternate import: StrengthSegment from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.StrengthSegment from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.StrengthSegment from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.StrengthSegment from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/strength-segment.tsx

### SignIn.PasswordFeedback

content · Element: p

What the estimator would say about this password, in its own words.

Its sentences come from the language pack, so they are already the person's language and
are not repeated in `copy`. Absent until there is something to say: a strong password
earns no advice, and an empty field has not been judged.

Import: @slices.sh/auth/headless

Capability: methods: password

PasswordFeedback must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"p", { warning: string | null; suggestions: readonly string[] }>
```

Alternate import: Feedback from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.PasswordFeedback from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.PasswordFeedback from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.PasswordFeedback from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/password-feedback.tsx

### SignIn.PasswordRequirement

content · Element: li

One rule a new password has to meet, said out loud rather than discovered by being
refused.

The numbers are the backend's, read from the capability, so the sentence and the refusal
can never disagree. `data-state` is `met` or `unmet`, which is what a skin ticks off. A
rule the backend does not have is absent: a "hard to guess" line over a backend that
accepts anything would be a promise nobody keeps.

Import: @slices.sh/auth/headless

Capability: methods: password

PasswordRequirement must be inside <SignIn.Root> or <SignUp.Root>.

```tsx
PartProps<"li", State> & { rule: PasswordRule }

export type PasswordRule = "length" | "strength"

type State = { met: boolean; minLength: number; maxLength: number }
```

Alternate import: Requirement from @slices.sh/auth/headless/password-settings

Alternate import: SignIn.PasswordRequirement from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.PasswordRequirement from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.PasswordRequirement from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/password-requirement.tsx

### SignIn.SubmitReset

action · Element: button

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.SubmitReset must be inside <SignIn.Root>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: SignIn.SubmitReset from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.SubmitReset from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.SubmitReset from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/submit-reset.tsx

### SignIn.ResetDone

gate

ForgotPassword opens the reset steps, and nothing unless the backend can deliver a link or a code. ResetMethod gates on method="link" | "code". RequestReset sends it with { pending, method }; ResetSent says it went, the same for any address. ResetDestination, ResetCodeInput and ResendResetCode are the code variant. ResetUnused gates while a link is unspent; NewPassword and SubmitReset set the password; ResetDone follows. BackToSignIn leaves and drops the token from the address bar.

Import: @slices.sh/auth/headless

Capability: password-reset

SignIn.ResetDone must be inside <SignIn.Root>.

```tsx
GateProps
```

Alternate import: SignIn.ResetDone from @slices.sh/auth/headless/sign-in

Alternate import: SignIn.ResetDone from @slices.sh/auth/headless/passkeys

Alternate import: SignIn.ResetDone from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/sign-in/reset-done.tsx
