{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "footer",
  "type": "registry:ui",
  "title": "Footer",
  "description": "Site footer with grouped link columns + copyright row. Composes with a primary brand mark, 1-4 link groups (each a sub-heading + list of links), and a tail row for copyright + social. Server component.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "navigation",
    "pattern"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/footer.tsx",
      "target": "components/ui/patterns/footer.tsx",
      "type": "registry:ui",
      "content": "import * as React from 'react';\n\n// @interlace/footer v1.0.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/footer\n// What changed since: https://ds.interlace.tools/c/footer#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Footer\n *\n * Site footer with grouped link columns + copyright row. Composes with\n * a primary brand mark, 1-4 link groups (each a sub-heading + list of\n * links), and a tail row for copyright + social. Server component.\n *\n * ## Anatomy\n *\n *   <footer data-slot=\"footer\" data-min-viewport=\"320\">\n *     <div className=\"container\">\n *       <div className=\"grid\">{logo}{groups.map(...)}</div>\n *       <div className=\"tail\">{copyright}{social}</div>\n *     </div>\n *   </footer>\n *\n * ## MIN_VIEWPORT — 320\n *\n * Link groups stack on mobile, grid out on md+. The tail row stacks too.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el                | `React.ComponentProps<'footer'>` + Footer props             |\n * | R6   | data-slot on root                | `data-slot=\"footer\"`                                        |\n * | R7   | className merged + ...rest       | `cn(BASE, className)` + `{...props}`                        |\n * | R10  | Composition seam (slots)         | `brand` / `groups` / `social` / `copyright` ReactNode props |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R18  | Tailwind only                    | Zero inline `style`; utility classes only                   |\n * | R19  | Tokens only                      | `bg-card` / `border-border` / `text-muted-foreground`       |\n * | R20  | AA contrast                      | semantic foregrounds                                        |\n * | R25  | Server component                 | No hooks                                                    |\n * | R26  | A11y from native el              | `<footer>` landmark, `<nav>` per group                      |\n */\n\nimport { cn } from '@/lib/utils';\n\nexport const MIN_VIEWPORT = 320 as const;\n\ninterface FooterLink {\n  href: string;\n  label: React.ReactNode;\n  external?: boolean;\n}\n\ninterface FooterGroup {\n  title: string;\n  links: FooterLink[];\n}\n\ninterface FooterProps extends React.ComponentProps<'footer'> {\n  /** Brand mark + tagline. Renders left of the link groups. */\n  brand?: React.ReactNode;\n  /** 1-4 grouped link columns. */\n  groups?: FooterGroup[];\n  /** Copyright line (left of the tail row). */\n  copyright?: React.ReactNode;\n  /** Social icons / extra links (right of the tail row). */\n  social?: React.ReactNode;\n  /** Container size. Defaults to `wide`. */\n  containerSize?: 'wide' | 'content' | 'prose';\n  /**\n   * Render-prop slot for internal links. Defaults to plain `<a>`. A\n   * Next.js consumer passes `({ href, className, children }) => <Link\n   * href={href} className={className}>{children}</Link>` for SPA\n   * navigation. External links always use plain `<a target=\"_blank\">`.\n   */\n  renderLink?: (props: {\n    href: string;\n    className: string;\n    children: React.ReactNode;\n  }) => React.ReactElement;\n}\n\nconst defaultRenderLink: NonNullable<FooterProps['renderLink']> = ({\n  href,\n  className,\n  children,\n}) => (\n  <a href={href} className={className}>\n    {children}\n  </a>\n);\n\nconst CONTAINER_CLASSES = {\n  wide: 'max-w-(--container-wide)',\n  content: 'max-w-(--container-content)',\n  prose: 'max-w-(--container-prose)',\n} as const;\n\nfunction Footer({\n  brand,\n  groups = [],\n  copyright,\n  social,\n  containerSize = 'wide',\n  renderLink = defaultRenderLink,\n  className,\n  ...props\n}: FooterProps) {\n  return (\n    <footer\n      data-slot=\"footer\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      className={cn(\n        'border-border bg-card text-card-foreground border-t',\n        className,\n      )}\n      {...props}\n    >\n      <div\n        className={cn(\n          'mx-auto px-md py-xl',\n          CONTAINER_CLASSES[containerSize],\n        )}\n      >\n        {brand || groups.length > 0 ? (\n          <div\n            className={cn(\n              'grid gap-lg',\n              groups.length === 1 && 'md:grid-cols-[1fr_1fr]',\n              groups.length === 2 && 'md:grid-cols-[1fr_1fr_1fr]',\n              groups.length === 3 && 'md:grid-cols-[1.5fr_1fr_1fr_1fr]',\n              groups.length >= 4 && 'md:grid-cols-[1.5fr_1fr_1fr_1fr_1fr]',\n            )}\n          >\n            {brand ? <div>{brand}</div> : null}\n            {groups.map((group, i) => (\n              <nav key={i} aria-label={group.title}>\n                <h2 className=\"font-body text-ui font-semibold mb-sm\">\n                  {group.title}\n                </h2>\n                <ul className=\"flex flex-col gap-xs text-sm\">\n                  {group.links.map((link) => (\n                    <li key={link.href}>\n                      {link.external ? (\n                        <a\n                          href={link.href}\n                          target=\"_blank\"\n                          rel=\"noopener noreferrer\"\n                          className=\"text-muted-foreground hover:text-foreground transition-colors\"\n                        >\n                          {link.label} ↗\n                        </a>\n                      ) : (\n                        renderLink({\n                          href: link.href,\n                          className:\n                            'text-muted-foreground hover:text-foreground transition-colors',\n                          children: link.label,\n                        })\n                      )}\n                    </li>\n                  ))}\n                </ul>\n              </nav>\n            ))}\n          </div>\n        ) : null}\n        {(copyright || social) ? (\n          <div className=\"border-border mt-lg flex flex-col items-center justify-between gap-sm border-t pt-md md:flex-row\">\n            {copyright ? (\n              <div className=\"text-muted-foreground text-sm\">{copyright}</div>\n            ) : null}\n            {social ? <div className=\"flex items-center gap-sm\">{social}</div> : null}\n          </div>\n        ) : null}\n      </div>\n    </footer>\n  );\n}\nFooter.displayName = 'Footer';\n\nexport { Footer };\nexport type { FooterProps, FooterGroup, FooterLink };\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": false,
    "minViewport": 320,
    "loading": false,
    "version": "1.0.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/footer\n\nInstalled to `components/ui/patterns/footer.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/footer';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/footer\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
