Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository
Blocks

Split sign-up

Jump to source

Create your account

Free for 14 days. No card required, and nothing renews on its own.

At least 8 characters, with a number and a symbol.

or

Already have an account? Sign in

Source

A two-column create-account screen: the form on one side, social proof and a feature list on the other. Built from Input, Checkbox, Button, Avatar, AvatarGroup, Progress and Separator.

import { useState } from "react";
import { Quote } from "lucide-react";
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  Button,
  Checkbox,
  Input,
  Progress,
  Separator,
} from "neelam-ui";

const strengthLabels = ["Too short", "Weak", "Fair", "Strong"] as const;

/** Deliberately crude — a real app would use zxcvbn or similar. */
function scorePassword(value: string): number {
  if (value.length < 8) return 0;
  let score = 1;
  if (/[A-Z]/.test(value) && /[a-z]/.test(value)) score += 1;
  if (/\d/.test(value) && /[^\w\s]/.test(value)) score += 1;
  return score;
}

export default function Signup01() {
  const [password, setPassword] = useState("");
  const score = scorePassword(password);

  return (
    <div className="grid min-h-full w-full lg:grid-cols-2">
      {/* Form first in the DOM so the keyboard and screen-reader path starts
          with the thing the page is for, whichever side it renders on. */}
      <div className="flex items-center justify-center px-4 py-12 sm:px-8">
        <div className="w-full max-w-sm">
          <h1 className="text-2xl font-bold tracking-tight text-slate-950 dark:text-white">
            Create your account
          </h1>
          <p className="mt-2 text-sm text-slate-600 dark:text-slate-400">
            Free for 14 days. No card required, and nothing renews on its own.
          </p>

          <form
            className="mt-8 flex flex-col gap-4"
            onSubmit={(event) => event.preventDefault()}
          >
            <div className="grid gap-4 sm:grid-cols-2">
              <label className="flex flex-col gap-1.5 text-sm font-medium text-slate-950 dark:text-white">
                First name
                <Input autoComplete="given-name" placeholder="Ada" />
              </label>
              <label className="flex flex-col gap-1.5 text-sm font-medium text-slate-950 dark:text-white">
                Last name
                <Input autoComplete="family-name" placeholder="Lovelace" />
              </label>
            </div>

            <label className="flex flex-col gap-1.5 text-sm font-medium text-slate-950 dark:text-white">
              Work email
              <Input
                type="email"
                autoComplete="email"
                placeholder="ada@northstar.io"
              />
            </label>

            <label className="flex flex-col gap-1.5 text-sm font-medium text-slate-950 dark:text-white">
              Password
              <Input
                type="password"
                autoComplete="new-password"
                value={password}
                onChange={(event) => setPassword(event.target.value)}
                aria-describedby="password-strength"
              />
            </label>

            {/* The bar is one signal and the words are another, so strength
                is never carried by colour or length alone. */}
            <div id="password-strength" className="flex flex-col gap-1.5">
              <Progress
                value={(score / 3) * 100}
                aria-label="Password strength"
                className="h-1.5"
              />
              <p className="text-xs text-slate-500 dark:text-slate-400">
                {password.length === 0
                  ? "At least 8 characters, with a number and a symbol."
                  : strengthLabels[score]}
              </p>
            </div>

            <label className="flex items-start gap-2.5 text-sm text-slate-700 dark:text-slate-300">
              <Checkbox className="mt-0.5" />
              <span>
                I agree to the{" "}
                <a href="#" className="underline underline-offset-4">
                  terms of service
                </a>{" "}
                and the{" "}
                <a href="#" className="underline underline-offset-4">
                  privacy policy
                </a>
                .
              </span>
            </label>

            <Button type="submit" className="w-full">
              Create account
            </Button>
          </form>

          <div className="my-6 flex items-center gap-3">
            <Separator className="flex-1" />
            <span className="text-xs uppercase tracking-wider text-slate-500 dark:text-slate-400">
              or
            </span>
            <Separator className="flex-1" />
          </div>

          <Button variant="outline" className="w-full">
            Continue with SSO
          </Button>

          <p className="mt-6 text-center text-sm text-slate-600 dark:text-slate-400">
            Already have an account?{" "}
            <a
              href="#"
              className="font-medium text-slate-950 underline underline-offset-4 dark:text-white"
            >
              Sign in
            </a>
          </p>
        </div>
      </div>

      {/* The marketing panel is supporting material, so it drops out entirely
          below `lg` rather than pushing the form down a screen. */}
      <aside className="hidden flex-col justify-center gap-8 border-l border-slate-200 bg-slate-50 px-12 py-12 lg:flex dark:border-slate-800 dark:bg-slate-900">
        <figure className="flex flex-col gap-6">
          <Quote
            className="h-8 w-8 text-slate-300 dark:text-slate-700"
            aria-hidden="true"
          />
          <blockquote className="text-xl font-medium leading-relaxed text-slate-950 dark:text-white">
            We replaced three internal dashboards with one Northstar workspace.
            The rollout took an afternoon.
          </blockquote>
          <figcaption className="flex items-center gap-3">
            <Avatar>
              <AvatarImage src="https://i.pravatar.cc/128?img=12" alt="" />
              <AvatarFallback>GH</AvatarFallback>
            </Avatar>
            <span className="flex flex-col text-sm">
              <span className="font-medium text-slate-950 dark:text-white">
                Grace Hopper
              </span>
              <span className="text-slate-500 dark:text-slate-400">
                VP Engineering, Contoso
              </span>
            </span>
          </figcaption>
        </figure>

        <Separator />

        <div className="flex items-center gap-4">
          {/* No `total` — the count is in the sentence beside it, and an
              overflow chip reading "+2,395" is a number nobody parses. */}
          <AvatarGroup label="Recent signups" max={4}>
            {["AL", "AT", "KJ", "MH", "RP"].map((initials) => (
              <Avatar key={initials} size="sm">
                <AvatarFallback>{initials}</AvatarFallback>
              </Avatar>
            ))}
          </AvatarGroup>
          <p className="text-sm text-slate-600 dark:text-slate-400">
            Joined by 2,400 teams in the last year.
          </p>
        </div>
      </aside>
    </div>
  );
}