# Sign-up card

> Centered sign-up card over a grid and light beams, with live password rules, terms and social sign-up.

A shadcn/ui block from the [@velora](https://velora.colorlib.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/velora/auth-signup-card).

- Page, with a live preview: https://shadcn.exchange/r/velora/auth-signup-card
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/velora/auth-signup-card-light-d6f0f02fd8c8c1c4.png
- Registry item: https://velora.colorlib.com/r/auth-signup-card.json
- Categories: auth-sections
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/velora/auth-signup-card.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `utils`, `button`, `input`, `label`, `https://velora.colorlib.com/r/background-beams.json`, `https://velora.colorlib.com/r/grid-pattern.json`

## Files

### `src/components/blocks/auth-signup-card.tsx`

```tsx
"use client";

import { useId, useState } from "react";
import { CheckIcon, CircleIcon, EyeIcon, EyeOffIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { BackgroundBeams } from "@/components/velora/background-beams";
import { GridPattern } from "@/components/velora/grid-pattern";
import { cn } from "@/lib/utils";

const BRAND = "Acme";
const TITLE = "Create your account";
const SUBTITLE = "Start a 14-day free trial. No credit card required.";
const LINKS = { login: "#", terms: "#", privacy: "#", home: "#" };

const RULES = [
  { label: "At least 8 characters", test: (value: string) => value.length >= 8 },
  { label: "One uppercase letter", test: (value: string) => /[A-Z]/.test(value) },
  { label: "One number", test: (value: string) => /\d/.test(value) },
  { label: "One symbol", test: (value: string) => /[^A-Za-z0-9]/.test(value) },
];

/**
 * Centered sign-up card over a grid and slow light beams. Password rules
 * tick off as you type. Submitting does nothing — wire `handleSubmit` to
 * your auth provider.
 */
export function AuthSignupCard() {
  const id = useId();
  const [password, setPassword] = useState("");
  const [showPassword, setShowPassword] = useState(false);
  const passed = RULES.filter((rule) => rule.test(password)).length;

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
  }

  return (
    <section className="relative isolate overflow-hidden bg-background px-6 py-20 sm:py-24 lg:px-8">
      <GridPattern
        width={44}
        height={44}
        squares={[
          [3, 2],
          [5, 4],
          [9, 1],
          [11, 6],
          [15, 3],
          [18, 8],
        ]}
        className="-z-10 fill-primary/5 [mask-image:radial-gradient(ellipse_70%_60%_at_top,black,transparent)]"
      />
      <BackgroundBeams className="-z-10 opacity-70" />
      <div
        aria-hidden
        className="absolute top-0 left-1/2 -z-10 h-80 w-2xl -translate-x-1/2 rounded-full bg-brand/10 blur-3xl"
      />

      <div className="mx-auto flex min-h-[44rem] max-w-md flex-col justify-center">
        <div className="text-center">
          <a
            href={LINKS.home}
            aria-label={`${BRAND} home`}
            className="inline-flex rounded-lg focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring"
          >
            <LogoMark />
          </a>
          <h2 className="mt-6 text-3xl font-semibold tracking-tight">{TITLE}</h2>
          <p className="mt-2 text-pretty text-muted-foreground">{SUBTITLE}</p>
        </div>

        <div className="mt-10 rounded-2xl border bg-card/80 p-6 shadow-xl shadow-brand/5 backdrop-blur sm:p-8">
          <div className="grid grid-cols-2 gap-3">
            <Button type="button" variant="outline" size="lg" className="h-10">
              <GitHubMark />
              GitHub
            </Button>
            <Button type="button" variant="outline" size="lg" className="h-10">
              <GoogleMark />
              Google
            </Button>
          </div>

          <div className="my-7 flex items-center gap-4 text-xs text-muted-foreground uppercase">
            <span aria-hidden className="h-px flex-1 bg-border" />
            or with email
            <span aria-hidden className="h-px flex-1 bg-border" />
          </div>

          <form onSubmit={handleSubmit} className="space-y-5">
            <div className="space-y-2">
              <Label htmlFor={`${id}-name`}>Full name</Label>
              <Input
                id={`${id}-name`}
                name="name"
                required
                autoComplete="name"
                placeholder="Ada Lovelace"
                className="h-10 px-3"
              />
            </div>

            <div className="space-y-2">
              <Label htmlFor={`${id}-email`}>Work email</Label>
              <Input
                id={`${id}-email`}
                name="email"
                type="email"
                required
                autoComplete="email"
                placeholder="you@company.com"
                className="h-10 px-3"
              />
            </div>

            <div className="space-y-2">
              <Label htmlFor={`${id}-password`}>Password</Label>
              <div className="relative">
                <Input
                  id={`${id}-password`}
                  name="password"
                  type={showPassword ? "text" : "password"}
                  required
                  minLength={8}
                  autoComplete="new-password"
                  value={password}
                  onChange={(event) => setPassword(event.target.value)}
                  aria-describedby={`${id}-rules`}
                  className="h-10 pr-11 pl-3"
                />
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  aria-label="Show password"
                  aria-pressed={showPassword}
                  aria-controls={`${id}-password`}
                  onClick={() => setShowPassword((shown) => !shown)}
                  className="absolute top-1 right-1 text-muted-foreground hover:text-foreground"
                >
                  {showPassword ? <EyeOffIcon aria-hidden /> : <EyeIcon aria-hidden />}
                </Button>
              </div>

              <div aria-hidden className="grid grid-cols-4 gap-1.5 pt-1">
                {RULES.map((rule, i) => (
                  <span
                    key={rule.label}
                    className={cn(
                      "h-1 rounded-full bg-muted transition-colors",
                      i < passed && "bg-gradient-to-r from-brand-from to-brand-to"
                    )}
                  />
                ))}
              </div>

              <ul id={`${id}-rules`} className="grid grid-cols-1 gap-x-4 gap-y-1.5 pt-1 text-xs sm:grid-cols-2">
                {RULES.map((rule) => {
                  const met = rule.test(password);
                  return (
                    <li
                      key={rule.label}
                      className={cn(
                        "flex items-center gap-1.5 transition-colors",
                        met ? "text-foreground" : "text-muted-foreground"
                      )}
                    >
                      {met ? (
                        <CheckIcon aria-hidden className="size-3.5 text-primary" />
                      ) : (
                        <CircleIcon aria-hidden className="size-3.5" />
                      )}
                      {rule.label}
                      <span className="sr-only">{met ? " (done)" : " (required)"}</span>
                    </li>
                  );
                })}
              </ul>
            </div>

            <div className="flex items-start gap-2.5">
              <input
                id={`${id}-terms`}
                name="terms"
                type="checkbox"
                required
                className="mt-0.5 size-4 shrink-0 cursor-pointer accent-primary dark:scheme-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
              />
              <Label htmlFor={`${id}-terms`} className="block leading-snug font-normal text-muted-foreground">
                I agree to the{" "}
                <a
                  href={LINKS.terms}
                  className="rounded-sm font-medium text-foreground underline underline-offset-4 hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
                >
                  Terms of Service
                </a>{" "}
                and{" "}
                <a
                  href={LINKS.privacy}
                  className="rounded-sm font-medium text-foreground underline underline-offset-4 hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
                >
                  Privacy Policy
                </a>
                .
              </Label>
            </div>

            <Button type="submit" size="lg" className="h-10 w-full">
              Create account
            </Button>
          </form>
        </div>

        <p className="mt-8 text-center text-sm text-muted-foreground">
          Already have an account?{" "}
          <a
            href={LINKS.login}
            className="rounded-sm font-medium text-foreground underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
          >
            Log in
          </a>
        </p>
      </div>
    </section>
  );
}

function LogoMark() {
  return (
    <svg aria-hidden viewBox="0 0 24 24" className="size-11">
      <rect width="24" height="24" rx="7" className="fill-primary" />
      <path
        d="M7 16.5 12 7l5 9.5"
        fill="none"
        strokeWidth="2.2"
        strokeLinecap="round"
        strokeLinejoin="round"
        className="stroke-primary-foreground"
      />
    </svg>
  );
}

function GitHubMark() {
  return (
    <svg aria-hidden viewBox="0 0 24 24" fill="currentColor">
      <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
    </svg>
  );
}

function GoogleMark() {
  return (
    <svg aria-hidden viewBox="0 0 24 24" fill="currentColor">
      <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09Z" />
      <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z" />
      <path d="M5.84 14.1A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.43.34-2.1V7.07H2.18A11 11 0 0 0 1 12c0 1.78.43 3.45 1.18 4.93l3.66-2.84Z" />
      <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1A11 11 0 0 0 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53Z" />
    </svg>
  );
}
```
