{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "signup-1",
  "title": "Signup 1: form with benefits column",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A signup page: validated name, work email and password with a live strength guide, terms checkbox, SSO option and a check-your-inbox confirmation, beside a benefits list with social proof.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/checkbox.json",
    "https://ui.ballmac.com/r/field.json",
    "https://ui.ballmac.com/r/input.json",
    "https://ui.ballmac.com/r/password-input.json",
    "https://ui.ballmac.com/r/media.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/signup-1/signup-1.tsx",
      "content": "// Ballmac UI: Signup 1. https://ui.ballmac.com/blocks/signup-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, Check, KeyRound, MailCheck } from \"lucide-react\"\n\nimport { Button } from \"@/components/ballmac/button\"\nimport { Checkbox } from \"@/components/ballmac/checkbox\"\nimport { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, useFieldControl } from \"@/components/ballmac/field\"\nimport { Input } from \"@/components/ballmac/input\"\nimport { PasswordInput } from \"@/components/ballmac/password-input\"\nimport { cn } from \"@/lib/utils\"\nimport { Media, type MediaSource } from \"@/components/ballmac/media\"\n\ntype Signup1Values = { name: string; email: string; password: string }\n\ntype Signup1Props = Omit<React.ComponentProps<\"section\">, \"title\" | \"onSubmit\"> & {\n  /** Brand name beside the logo mark. */\n  brand?: string\n  /** Your logo mark instead of the brand's first letter: an image URL, an object with alt text and a dark-mode file, or your own element. A plain URL is described by the brand name. */\n  logo?: MediaSource\n  /** Heading on the form. */\n  title?: string\n  /** One sentence under the heading. */\n  description?: string\n  /** Label on the submit button. */\n  buttonLabel?: string\n  /** Called with the values once they are valid. Throw to show an error; resolve to show the confirmation. */\n  onSubmit?: (values: Signup1Values) => void | Promise<void>\n  /** Called by \"Continue with SSO\". Pass null to hide the button. */\n  onSso?: (() => void) | null\n  /** Heading of the benefits column. */\n  pitch?: string\n  /** Short benefits shown beside the form on large screens. */\n  benefits?: string[]\n  /** Social proof line under the benefits. */\n  proof?: string\n  /** Where \"Sign in\" goes. */\n  loginHref?: string\n  /** Where the terms and privacy links go. */\n  termsHref?: string\n}\n\nfunction FieldInput(props: React.ComponentProps<typeof Input>) {\n  return <Input {...useFieldControl()} {...props} />\n}\nfunction FieldPassword(props: React.ComponentProps<typeof PasswordInput>) {\n  return <PasswordInput {...useFieldControl()} {...props} />\n}\n\nconst emailPattern = /^\\S+@\\S+\\.\\S+$/\n\nfunction Signup1({\n  brand = \"Acme\",\n  logo,\n  title = \"Create your account\",\n  description = \"Free for your first three teammates. No card needed.\",\n  buttonLabel = \"Create account\",\n  onSubmit,\n  onSso = () => {},\n  pitch = \"Everything your team needs to get paid faster.\",\n  benefits = [\"Send your first invoice in under an hour\", \"Approvals, reminders and reports built in\", \"Bank-level security and a full audit trail\", \"Switch plans or cancel at any time\"],\n  proof = \"Joined by 12,400 teams this year\",\n  loginHref = \"#\",\n  termsHref = \"#\",\n  className,\n  ...props\n}: Signup1Props) {\n  const [v, setV] = React.useState({ name: \"\", email: \"\", password: \"\" })\n  const [agree, setAgree] = React.useState(false)\n  const [touched, setTouched] = React.useState<Record<string, boolean>>({})\n  const [state, setState] = React.useState<\"idle\" | \"sending\" | \"error\" | \"done\">(\"idle\")\n  const errors = {\n    name: v.name.trim() ? undefined : \"Tell us your name.\",\n    email: emailPattern.test(v.email) ? undefined : \"Enter a full email address, like name@company.com.\",\n    password: v.password.length >= 8 ? undefined : \"Use at least 8 characters.\",\n    agree: agree ? undefined : \"Please accept the terms to continue.\",\n  }\n  const show = (k: keyof typeof errors) => (touched[k] ? errors[k] : undefined)\n\n  async function submit(e: React.FormEvent) {\n    e.preventDefault()\n    setTouched({ name: true, email: true, password: true, agree: true })\n    if (Object.values(errors).some(Boolean)) return\n    setState(\"sending\")\n    try {\n      if (onSubmit) await onSubmit(v)\n      else await new Promise((r) => setTimeout(r, 900))\n      setState(\"done\")\n    } catch {\n      setState(\"error\")\n    }\n  }\n\n  return (\n    <section data-slot=\"signup-1\" className={cn(\"mx-auto grid max-w-6xl items-center gap-12 px-4 py-14 sm:px-6 md:py-20 lg:grid-cols-[1fr_28rem] lg:gap-20\", className)} {...props}>\n      <div className=\"hidden lg:block\">\n        <a href=\"#\" className=\"focus-visible:ring-ring/50 inline-flex items-center gap-2.5 rounded-md text-lg font-semibold tracking-tight outline-none focus-visible:ring-[3px]\">\n          {logo ? <Media media={logo} alt={brand} aspect=\"square\" fit=\"contain\" priority className=\"size-7 shrink-0 rounded-lg\" /> : <span aria-hidden=\"true\" className=\"bg-foreground text-background flex size-7 items-center justify-center rounded-lg text-sm font-bold\">{brand[0]}</span>}\n          {brand}\n        </a>\n        <h2 className=\"mt-10 max-w-md text-4xl font-semibold tracking-[-0.04em] text-balance xl:text-5xl xl:leading-[1.05]\">{pitch}</h2>\n        {benefits.length > 0 && (\n        <ul className=\"mt-8 space-y-4\">\n          {benefits.map((b) => (\n            <li key={b} className=\"flex items-start gap-3\">\n              <span className=\"bg-chart-2/15 mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full\"><Check className=\"size-3.5\" aria-hidden=\"true\" /></span>\n              <span className=\"text-pretty\">{b}</span>\n            </li>\n          ))}\n        </ul>\n        )}\n        <div className=\"mt-10 flex items-center gap-3\">\n          <div className=\"flex -space-x-1\" aria-hidden=\"true\">\n            {[\"bg-chart-1/30\", \"bg-chart-3/30\", \"bg-chart-5/30\", \"bg-chart-2/30\"].map((c, i) => (\n              <span key={c} className={cn(\"ring-background flex size-8 items-center justify-center rounded-full text-[11px] font-semibold ring-2\", c)}>{[\"MK\", \"JO\", \"AS\", \"EF\"][i]}</span>\n            ))}\n          </div>\n          <p className=\"text-muted-foreground text-sm\">{proof}</p>\n        </div>\n      </div>\n\n      <div className=\"bg-card relative mx-auto w-full max-w-md rounded-3xl border p-6 shadow-[0_30px_80px_-50px_rgb(0_0_0/0.4)] sm:p-8 lg:max-w-none\">\n        {state === \"done\" ? (\n          <div role=\"status\" className=\"flex min-h-[26rem] flex-col items-center justify-center text-center\">\n            <span className=\"bg-chart-2/15 flex size-14 items-center justify-center rounded-full\"><MailCheck className=\"size-7\" aria-hidden=\"true\" /></span>\n            <h2 className=\"mt-6 text-2xl font-semibold tracking-[-0.02em]\">Check your inbox</h2>\n            <p className=\"text-muted-foreground mt-2 max-w-xs text-pretty\">We sent a confirmation link to <span className=\"text-foreground font-medium\">{v.email}</span>. It expires in 24 hours.</p>\n            <a href={loginHref} className=\"text-foreground focus-visible:ring-ring/50 mt-8 rounded-sm text-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-[3px]\">Back to sign in</a>\n          </div>\n        ) : (\n          <>\n            <h1 className=\"text-2xl font-semibold tracking-[-0.03em]\">{title}</h1>\n            <p className=\"text-muted-foreground mt-1.5 text-sm text-pretty\">{description}</p>\n            {onSso && (\n              <>\n                <Button variant=\"outline\" className=\"mt-6 w-full\" onClick={onSso}><KeyRound /> Continue with SSO</Button>\n                <div className=\"text-muted-foreground my-5 flex items-center gap-3 text-xs\" aria-hidden=\"true\">\n                  <span className=\"bg-border h-px flex-1\" />or with email<span className=\"bg-border h-px flex-1\" />\n                </div>\n              </>\n            )}\n            <form noValidate onSubmit={submit} aria-label=\"Create account\" className={cn(!onSso && \"mt-6\")}>\n              <FieldGroup>\n                {state === \"error\" && <p role=\"alert\" className=\"border-destructive/40 text-destructive rounded-lg border px-3 py-2.5 text-sm\">We couldn’t create your account. Please try again.</p>}\n                <Field invalid={!!show(\"name\")}>\n                  <FieldLabel required>Full name</FieldLabel>\n                  <FieldInput autoComplete=\"name\" placeholder=\"Jordan Lee\" value={v.name} onChange={(e) => setV({ ...v, name: e.target.value })} onBlur={() => setTouched((t) => ({ ...t, name: true }))} />\n                  <FieldError errors={[show(\"name\")]} />\n                </Field>\n                <Field invalid={!!show(\"email\")}>\n                  <FieldLabel required>Work email</FieldLabel>\n                  <FieldInput type=\"email\" autoComplete=\"email\" placeholder=\"jordan@company.com\" value={v.email} onChange={(e) => setV({ ...v, email: e.target.value })} onBlur={() => setTouched((t) => ({ ...t, email: true }))} />\n                  <FieldError errors={[show(\"email\")]} />\n                </Field>\n                <Field invalid={!!show(\"password\")}>\n                  <FieldLabel required>Password</FieldLabel>\n                  <FieldPassword value={v.password} onValueChange={(password) => setV({ ...v, password })} onBlur={() => setTouched((t) => ({ ...t, password: true }))} />\n                  <FieldError errors={[show(\"password\")]} />\n                  {!v.password && <FieldDescription>At least 8 characters.</FieldDescription>}\n                </Field>\n                <Field orientation=\"horizontal\" invalid={!!show(\"agree\")}>\n                  <Checkbox id=\"signup-1-agree\" checked={agree} onCheckedChange={(c) => { setAgree(c === true); setTouched((t) => ({ ...t, agree: true })) }} />\n                  <div className=\"grid gap-1\">\n                    <FieldLabel htmlFor=\"signup-1-agree\" className=\"font-normal\">\n                      <span>I agree to the <a href={termsHref} className=\"underline underline-offset-4\">Terms</a> and <a href={termsHref} className=\"underline underline-offset-4\">Privacy Policy</a>.</span>\n                    </FieldLabel>\n                    <FieldError errors={[show(\"agree\")]} />\n                  </div>\n                </Field>\n                <Button type=\"submit\" size=\"lg\" shape=\"pill\" loading={state === \"sending\"} className=\"w-full\">\n                  {buttonLabel} <ArrowRight  className=\"rtl:rotate-180\"/>\n                </Button>\n              </FieldGroup>\n            </form>\n            <p className=\"text-muted-foreground mt-6 text-center text-sm\">\n              Already have an account?{\" \"}\n              <a href={loginHref} className=\"text-foreground focus-visible:ring-ring/50 rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-[3px]\">Sign in</a>\n            </p>\n          </>\n        )}\n      </div>\n    </section>\n  )\n}\n\nexport { Signup1, type Signup1Props, type Signup1Values }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/signup-1/signup-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "signup",
      "register",
      "auth",
      "password strength",
      "form",
      "sso"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "A registration page. Pass onSubmit(values) (throw to show an error), onSso (null hides), and benefits / pitch / proof for the left column.",
      "whenToUse": [
        "Self-serve SaaS signup",
        "Pages that should sell while they collect details"
      ],
      "whenNotToUse": [
        "Invitation acceptance (use invite-1)",
        "Multi-step setup (use onboarding-1)"
      ],
      "composesWith": [
        "login-2",
        "verify-1",
        "onboarding-1",
        "header-2"
      ],
      "a11y": [
        {
          "keys": "Submit with errors",
          "action": "Each error is announced and linked to its field"
        },
        {
          "keys": "Password field",
          "action": "The strength guide is announced politely as you type"
        }
      ],
      "customization": [
        "onSubmit(values) async; throw for the error state",
        "benefits, pitch, proof",
        "onSso: null hides the SSO button"
      ]
    },
    "examples": [
      "signup-1-demo",
      "signup-1-simple"
    ],
    "url": "https://ui.ballmac.com/components/signup-1"
  },
  "categories": [
    "blocks",
    "auth"
  ],
  "type": "registry:block"
}