{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "newsletter",
  "type": "registry:block",
  "title": "Newsletter",
  "description": "An email sign-up (newsletter subscribe) form. It announces the result to screen readers through a live region that is on the page from the start.",
  "categories": [
    "section"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/button.json",
    "https://facadeui.dev/r/container.json",
    "https://facadeui.dev/r/input.json",
    "https://facadeui.dev/r/section.json",
    "https://facadeui.dev/r/section-header.json",
    "https://facadeui.dev/r/types.json",
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/sections/newsletter.tsx",
      "type": "registry:component",
      "target": "components/sections/newsletter.tsx",
      "content": "\"use client\"\n\n/**\n * Newsletter — an email capture band.\n *\n * Presentation and wiring only: it owns no network call. Pass `onSubmit` and\n * drive `status` from whatever your form action or mutation returns, so the\n * section works the same behind a server action, a fetch, or a third-party\n * embed.\n *\n * a11y: a form that reports its own outcome, which is the part most newsletter\n * sections skip.\n *\n *  - The result is announced through a polite live region that is present from\n *    the first render. A live region inserted *at the same time* as its message\n *    is frequently missed by screen readers, so the container is always there\n *    and only its content changes.\n *  - While submitting, the button is `aria-busy` and keeps its label — the\n *    accessible name never changes under the user.\n *  - An error is passed to the field as well as the live region, so it is both\n *    announced and programmatically tied to the input that caused it.\n *  - The label is real. `hideLabel` moves it out of the layout, not out of the\n *    accessibility tree, which is what a placeholder-only field does.\n *\n * Dependencies: react, @/lib/types, @/lib/utils,\n * @/components/ui/button, @/components/ui/container, @/components/ui/input,\n * @/components/ui/section, @/components/ui/section-header.\n */\n\nimport type { FormEvent, ReactNode } from \"react\"\n\nimport type { SectionBaseProps } from \"@/lib/types\"\nimport { cn, slugId } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Container } from \"@/components/ui/container\"\nimport { Input } from \"@/components/ui/input\"\nimport { Section } from \"@/components/ui/section\"\nimport { SectionHeader } from \"@/components/ui/section-header\"\n\nexport type NewsletterStatus = \"idle\" | \"submitting\" | \"success\" | \"error\"\n\nexport interface NewsletterProps extends SectionBaseProps {\n  title: string\n  description?: string\n  eyebrow?: string\n  /** Called with the submitted address. Make the network request in your own code. */\n  onSubmit?: (email: string) => void\n  /** Controls the button, the live region, and the field's error state. */\n  status?: NewsletterStatus\n  /** Announced on success. */\n  successMessage?: ReactNode\n  /** Announced on failure and attached to the field. */\n  errorMessage?: string\n  label?: string\n  hideLabel?: boolean\n  placeholder?: string\n  submitLabel?: string\n  /** Small print under the form, such as consent, frequency or how to unsubscribe. */\n  note?: ReactNode\n  /** `inline` puts the button inside the field; `stacked` puts it below. */\n  layout?: \"inline\" | \"stacked\"\n  variant?: \"plain\" | \"muted\" | \"card\"\n  align?: \"start\" | \"center\"\n}\n\nconst surfaces = {\n  plain: \"\",\n  muted: \"bg-muted rounded-2xl px-6 py-12 sm:px-12\",\n  card: \"bg-card text-card-foreground rounded-2xl border px-6 py-12 sm:px-12\",\n} as const\n\nexport function Newsletter({\n  title,\n  description,\n  eyebrow,\n  onSubmit,\n  status = \"idle\",\n  successMessage = \"Thanks — check your inbox to confirm.\",\n  errorMessage,\n  label = \"Email address\",\n  hideLabel = true,\n  placeholder = \"you@example.com\",\n  submitLabel = \"Subscribe\",\n  note,\n  layout = \"inline\",\n  variant = \"muted\",\n  align = \"center\",\n  headingLevel = 2,\n  as = \"section\",\n  spacing = \"md\",\n  className,\n  id,\n}: NewsletterProps) {\n  const titleId = id ? `${id}-title` : slugId(title)\n  const submitting = status === \"submitting\"\n  const centered = align === \"center\"\n\n  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault()\n    if (!onSubmit) return\n    const data = new FormData(event.currentTarget)\n    onSubmit(String(data.get(\"email\") ?? \"\"))\n  }\n\n  return (\n    <Section as={as} spacing={spacing} labelledBy={titleId} id={id} className={className}>\n      <Container size=\"md\">\n        <div\n          className={cn(\n            \"flex flex-col gap-8\",\n            surfaces[variant],\n            centered && \"items-center\",\n          )}\n        >\n          <SectionHeader\n            align={align}\n            eyebrow={eyebrow}\n            title={title}\n            description={description}\n            headingLevel={headingLevel}\n            titleId={titleId}\n          />\n\n          <form\n            onSubmit={handleSubmit}\n            noValidate\n            className={cn(\n              \"flex w-full max-w-md flex-col gap-3\",\n              centered && \"items-center\",\n            )}\n          >\n            <div\n              className={cn(\n                \"flex w-full gap-3\",\n                layout === \"stacked\" && \"flex-col\",\n                layout === \"inline\" && \"flex-col sm:flex-row\",\n              )}\n            >\n              <Input\n                name=\"email\"\n                type=\"email\"\n                autoComplete=\"email\"\n                required\n                label={label}\n                hideLabel={hideLabel}\n                placeholder={placeholder}\n                error={status === \"error\" ? errorMessage : undefined}\n                disabled={submitting}\n              />\n              <Button\n                type=\"submit\"\n                loading={submitting}\n                loadingLabel=\"Subscribing\"\n                className={cn(\n                  \"h-11 shrink-0\",\n                  layout === \"stacked\" ? \"w-full\" : \"w-full sm:w-auto\",\n                )}\n              >\n                {submitLabel}\n              </Button>\n            </div>\n\n            {note ? (\n              <p\n                className={cn(\n                  \"text-muted-foreground text-pretty text-sm\",\n                  centered && \"text-center\",\n                )}\n              >\n                {note}\n              </p>\n            ) : null}\n\n            {/*\n              Always rendered, never conditionally mounted: a live region that\n              appears at the same moment as its message is routinely missed.\n            */}\n            <p\n              aria-live=\"polite\"\n              className={cn(\n                \"text-pretty text-sm\",\n                status === \"error\" ? \"text-destructive\" : \"text-muted-foreground\",\n                centered && \"text-center\",\n              )}\n            >\n              {status === \"success\" ? successMessage : null}\n              {status === \"error\" ? errorMessage : null}\n            </p>\n          </form>\n        </div>\n      </Container>\n    </Section>\n  )\n}\n"
    }
  ],
  "docs": "Import styles/facade-tokens.css in your global stylesheet, after @import \"tailwindcss\". Set headingLevel to fit the page outline (sections default to h2). Pass next/link and next/image through the link and image props. Docs: https://facadeui.dev/components/newsletter"
}
