---
title: "Login"
description: "A sign-in form with typed submission callback."
---

Interactive preview: https://ui.coderocket.app/docs/blocks/login

## Usage

Get this block from a [saved library export](/docs/export) or the [connected CLI](/docs/agents). The imports below refer to local files installed in your app.

Import `styles/components.css`, then `styles/blocks.css`, then `styles/theme.css` once. Wrap your app in `ThemeScope` from `components/ui/utils` so typography, focus and overlays share the theme. The [installation guide](/docs/export) covers runtime dependencies and React/Next.js setup.

This import is the entry point, not a complete integration example. Supply the required props and connect callbacks to your application. The full ZIP includes `examples/blocks-preview.tsx` with the populated preview and local sample callbacks; adapt those examples to your product.

```tsx
import { LoginBlock } from './components/blocks/auth';

// See Source and props below for the complete typed interface.
```

## Design system

This block consumes your semantic colors, spacing, typography and shape tokens. Import the compiled styles and your theme once as described in [Export](/docs/export).

Icons use Lucide React. Install `lucide-react@1.47.0` alongside React and Base UI.

Dependencies: [Button](/docs/components/button), [Card](/docs/components/card), [Field](/docs/components/field), [Input](/docs/components/input), [Input Group](/docs/components/input-group), [Alert](/docs/components/alert).

## Behavior and accessibility

- Provide meaningful labels for interactive controls.
- Connect action callbacks to your application logic.
- Verify keyboard navigation and contrast in your application.

Status: **experimental** during early access. Sample previews do not send emails, process payments or upload files to a service.

## Source and props

This is the exported source, using the same implementation as the editor. The complete source group is included below; related blocks share it to avoid duplicating behavior.

```tsx
"use client";
import { useId, useState, type CSSProperties, type ReactNode } from "react";
import {
  ArrowLeft,
  ArrowRight,
  Eye,
  EyeOff,
  KeyRound,
  LockKeyhole,
  Mail,
  ShieldCheck,
  UserRound,
} from "lucide-react";
import { Button, Card, Field, Input, InputGroup } from '../ui';
import { ActionForm, type FormAction } from "./common";

function AuthFrame({
  title,
  description,
  eyebrow,
  icon,
  footer,
  children,
}: {
  title: string;
  description: string;
  eyebrow: string;
  icon: ReactNode;
  footer?: ReactNode;
  children: ReactNode;
}) {
  const titleId = useId();
  return (
    <div className="cr-block-form cr-auth-shell">
      <Card className="cr-auth-card" aria-labelledby={titleId} footer={footer}>
        <header className="cr-auth-header">
          <div className="cr-auth-intro">
            <span className="cr-auth-symbol" aria-hidden="true">
              {icon}
            </span>
            <span className="cr-auth-eyebrow">{eyebrow}</span>
          </div>
          <h2 id={titleId} className="cr-auth-title">
            {title}
          </h2>
          <p className="cr-auth-description">{description}</p>
        </header>
        {children}
      </Card>
    </div>
  );
}

function PasswordInput({
  name,
  autoComplete,
  minLength,
  placeholder = "Enter your password",
}: {
  name: string;
  autoComplete: "current-password" | "new-password";
  minLength?: number;
  placeholder?: string;
}) {
  const [visible, setVisible] = useState(false);
  return (
    <InputGroup
      name={name}
      type={visible ? "text" : "password"}
      autoComplete={autoComplete}
      minLength={minLength}
      required
      placeholder={placeholder}
      leading={<LockKeyhole size={16} aria-hidden="true" />}
      trailing={
        <Button
          type="button"
          variant="ghost"
          className="cr-auth-password-toggle"
          aria-label={visible ? "Hide password" : "Show password"}
          aria-pressed={visible}
          onClick={() => setVisible(!visible)}
        >
          {visible ? (
            <EyeOff size={16} aria-hidden="true" />
          ) : (
            <Eye size={16} aria-hidden="true" />
          )}
        </Button>
      }
    />
  );
}

export function LoginBlock({
  onSubmit,
  signupHref,
  forgotHref,
}: {
  onSubmit: FormAction;
  signupHref: string;
  forgotHref: string;
}) {
  return (
    <AuthFrame
      title="Welcome back."
      description="Sign in to your workspace and pick up where you left off."
      eyebrow="Your workspace"
      icon={<LockKeyhole size={24} strokeWidth={1.75} />}
      footer={
        <p className="cr-auth-footer">
          New here?{" "}
          <a className="cr-auth-link" href={signupHref}>
            Create an account <ArrowRight size={14} aria-hidden="true" />
          </a>
        </p>
      }
    >
      <ActionForm
        onSubmit={onSubmit}
        submitLabel="Sign in"
        submitIcon={<ArrowRight size={17} aria-hidden="true" />}
        successMessage="Signed in."
      >
        <Field label="Email address">
          <InputGroup
            name="email"
            type="email"
            autoComplete="email"
            required
            placeholder="you@company.com"
            leading={<Mail size={16} aria-hidden="true" />}
          />
        </Field>
        <Field label="Password">
          <PasswordInput name="password" autoComplete="current-password" />
        </Field>
        <div className="cr-auth-assistance">
          <a className="cr-auth-link" href={forgotHref}>
            Forgot password?
          </a>
        </div>
      </ActionForm>
    </AuthFrame>
  );
}

export function SignupBlock({
  onSubmit,
  loginHref,
}: {
  onSubmit: FormAction;
  loginHref: string;
}) {
  return (
    <AuthFrame
      title="Make room for what’s next."
      description="Create your account. A fresh workspace starts here."
      eyebrow="A new beginning"
      icon={<UserRound size={23} strokeWidth={1.75} />}
      footer={
        <p className="cr-auth-footer">
          Already have an account?{" "}
          <a className="cr-auth-link" href={loginHref}>
            Sign in <ArrowRight size={14} aria-hidden="true" />
          </a>
        </p>
      }
    >
      <ActionForm
        onSubmit={onSubmit}
        submitLabel="Create account"
        submitIcon={<ArrowRight size={17} aria-hidden="true" />}
        successMessage="Check your inbox to confirm your email address."
      >
        <Field label="Full name">
          <InputGroup
            name="name"
            autoComplete="name"
            required
            placeholder="Your full name"
            leading={<UserRound size={16} aria-hidden="true" />}
          />
        </Field>
        <Field label="Email address">
          <InputGroup
            name="email"
            type="email"
            autoComplete="email"
            required
            placeholder="you@company.com"
            leading={<Mail size={16} aria-hidden="true" />}
          />
        </Field>
        <Field label="Password" description="Use at least 12 characters.">
          <PasswordInput
            name="password"
            autoComplete="new-password"
            minLength={12}
            placeholder="Create a password"
          />
        </Field>
      </ActionForm>
    </AuthFrame>
  );
}

export function ForgotPasswordBlock({
  onSubmit,
  loginHref,
}: {
  onSubmit: FormAction;
  loginHref: string;
}) {
  return (
    <AuthFrame
      title="Let’s get you back in."
      description="Enter your account’s email address. We’ll send you a link to choose a new password."
      eyebrow="Account recovery"
      icon={<KeyRound size={24} strokeWidth={1.75} />}
      footer={
        <p className="cr-auth-footer">
          <a className="cr-auth-link" href={loginHref}>
            <ArrowLeft size={14} aria-hidden="true" /> Back to sign in
          </a>
        </p>
      }
    >
      <ActionForm
        onSubmit={onSubmit}
        submitLabel="Send reset link"
        submitIcon={<ArrowRight size={17} aria-hidden="true" />}
        successMessage="If this address has an account, a reset link is on its way."
      >
        <Field label="Email address">
          <InputGroup
            name="email"
            type="email"
            autoComplete="email"
            required
            placeholder="you@company.com"
            leading={<Mail size={16} aria-hidden="true" />}
          />
        </Field>
      </ActionForm>
    </AuthFrame>
  );
}

export function ResetPasswordBlock({ onSubmit }: { onSubmit: FormAction }) {
  return (
    <AuthFrame
      title="A fresh start."
      description="Choose a new password to get back to your workspace."
      eyebrow="Reset password"
      icon={<ShieldCheck size={24} strokeWidth={1.75} />}
    >
      <ActionForm
        onSubmit={onSubmit}
        validate={(data) =>
          data.get("password") !== data.get("confirmation")
            ? "Passwords do not match. Enter the same password in both fields."
            : undefined
        }
        submitLabel="Update password"
        submitIcon={<ArrowRight size={17} aria-hidden="true" />}
        successMessage="Your password has been updated."
      >
        <Field label="New password" description="Use at least 12 characters.">
          <PasswordInput
            name="password"
            autoComplete="new-password"
            minLength={12}
            placeholder="Create a new password"
          />
        </Field>
        <Field label="Confirm password">
          <PasswordInput
            name="confirmation"
            autoComplete="new-password"
            minLength={12}
            placeholder="Enter it one more time"
          />
        </Field>
      </ActionForm>
    </AuthFrame>
  );
}

export function OtpBlock({
  onSubmit,
  length = 6,
}: {
  onSubmit: FormAction;
  length?: number;
}) {
  const size = Number.isFinite(length)
    ? Math.max(4, Math.min(10, Math.floor(length)))
    : 6;
  return (
    <AuthFrame
      title="Check your inbox."
      description={`Enter the ${size}-digit verification code from your email to continue.`}
      eyebrow="One more step"
      icon={<Mail size={24} strokeWidth={1.75} />}
    >
      <ActionForm
        onSubmit={onSubmit}
        submitLabel="Verify code"
        submitIcon={<ArrowRight size={17} aria-hidden="true" />}
        successMessage="Code verified."
      >
        <Field
          label="Verification code"
          description="You can paste the entire code into this field."
        >
          <Input
            name="code"
            inputMode="numeric"
            autoComplete="one-time-code"
            minLength={size}
            maxLength={size}
            pattern={`[0-9]{${size}}`}
            required
            className="cr-otp"
            placeholder={"0".repeat(size)}
            style={{ "--cr-otp-length": size } as CSSProperties}
          />
        </Field>
      </ActionForm>
    </AuthFrame>
  );
}
```
