{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "figure",
  "type": "registry:ui",
  "title": "Figure",
  "description": "Semantic media block. `<figure>` is the right element for *self-contained* media (image, diagram, code screenshot, chart) with an optional caption that scrolls with it. Composes `AspectRatio` so the layout reserves space before the asset loads — preventing…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "blog",
    "pattern"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/aspect-ratio.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/figure.tsx",
      "target": "components/ui/patterns/figure.tsx",
      "type": "registry:ui",
      "content": "import * as React from 'react';\n\n// @interlace/figure v1.0.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/figure\n// What changed since: https://ds.interlace.tools/c/figure#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Figure + FigureCaption\n *\n * Semantic media block. `<figure>` is the right element for *self-contained*\n * media (image, diagram, code screenshot, chart) with an optional caption\n * that scrolls with it. Composes `AspectRatio` so the layout reserves space\n * before the asset loads — preventing the cumulative-layout-shift class of\n * bugs that the LOADING_PHILOSOPHY explicitly forbids.\n *\n * `alt` is **required** at the type level. There is no opt-out: a media block\n * without alt text is an a11y bug per WCAG 1.1.1, and the type system makes\n * it a compile error instead of a lint warning.\n *\n * ## Anatomy\n *\n *   Figure                              (figure — data-min-viewport=320)\n *     ├─ AspectRatio ratio=16/9         (frame, prevents CLS)\n *     │   └─ {children ?? <img src alt>}\n *     └─ FigureCaption                  (figcaption — auto when `caption` set)\n *\n * ## MIN_VIEWPORT — 320\n *\n * Inline media must remain legible on the smallest supported viewport;\n * AspectRatio scales to whatever width the parent offers, and the caption\n * is a plain text node that flows naturally.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el                | `React.ComponentProps<'figure'>` / `'figcaption'`           |\n * | R6   | data-slot on root                | `data-slot=\"figure\"` / `data-slot=\"figure-caption\"`         |\n * | R7   | className merged + ...rest       | `cn(BASE, className)` + `{...props}`                        |\n * | R8   | No isXxx; enums only             | n/a — no boolean variants                                   |\n * | R10  | Composition seam                 | `children` overrides default `<img>` for diagrams / embeds  |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R18  | Tailwind only                    | Zero inline style                                           |\n * | R19  | Tokens only                      | `my-lg` / `space-y-sm` / `text-muted-foreground`            |\n * | R20  | AA contrast                      | Caption uses `--muted-foreground` (clears AA on background) |\n * | R25  | Server component                 | No hooks → no `'use client'`                                |\n * | R26  | A11y from native el              | `<figure>` + `<figcaption>` + required `alt`                |\n */\n\nimport { cn } from '@/lib/utils';\nimport { AspectRatio } from '@/components/ui/aspect-ratio';\n\nexport const MIN_VIEWPORT = 320 as const;\n\ntype FigureProps = Omit<React.ComponentProps<'figure'>, 'children'> & {\n  /** Image source URL. Ignored when `children` is provided. */\n  src?: string;\n  /**\n   * Alt text — **required**. WCAG 1.1.1 Non-text Content. For purely\n   * decorative figures pass an empty string explicitly; the type system\n   * makes the choice explicit at the call site.\n   */\n  alt: string;\n  /** Width / height ratio passed to AspectRatio. @default 16 / 9 */\n  ratio?: number;\n  /** Caption text — rendered inside a `<figcaption>` after the media. */\n  caption?: React.ReactNode;\n  /**\n   * Override the default `<img>` with a custom node (next/image, video,\n   * SVG diagram, embed). When set, `src` is ignored.\n   */\n  children?: React.ReactNode;\n};\n\nconst Figure = React.forwardRef<HTMLElement, FigureProps>(function Figure(\n  { className, src, alt, ratio = 16 / 9, caption, children, ...props },\n  ref,\n) {\n  return (\n    <figure\n      ref={ref}\n      data-slot=\"figure\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      className={cn('my-lg space-y-sm', className)}\n      {...props}\n    >\n      <AspectRatio ratio={ratio}>\n        {children ?? (\n          <img\n            src={src}\n            alt={alt}\n            className=\"absolute inset-0 size-full rounded-md object-cover\"\n          />\n        )}\n      </AspectRatio>\n      {caption ? <FigureCaption>{caption}</FigureCaption> : null}\n    </figure>\n  );\n});\nFigure.displayName = 'Figure';\n\ntype FigureCaptionProps = React.ComponentProps<'figcaption'>;\n\nconst FigureCaption = React.forwardRef<HTMLElement, FigureCaptionProps>(\n  function FigureCaption({ className, ...props }, ref) {\n    return (\n      <figcaption\n        ref={ref}\n        data-slot=\"figure-caption\"\n        className={cn('text-sm text-muted-foreground', className)}\n        {...props}\n      />\n    );\n  },\n);\nFigureCaption.displayName = 'FigureCaption';\n\nexport { Figure, FigureCaption };\nexport type { FigureProps, FigureCaptionProps };\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": false,
    "minViewport": 320,
    "loading": false,
    "version": "1.0.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/figure\n\nInstalled to `components/ui/patterns/figure.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/figure';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/figure\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
