{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "container-card",
  "type": "registry:ui",
  "title": "Container Card",
  "description": "A self-adapting card that restyles based on its own inline width, not the viewport. The same markup stacks media above text in a narrow column or sidebar and lays them side-by-side in a wide column or grid cell — built on CSS container queries (container-type: inline-size + @container). Pure CSS, zero JavaScript.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/container-card.tsx",
      "type": "registry:ui",
      "target": "components/ui/container-card.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport type { Child, PropsWithChildren } from \"hono/jsx\"\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Container Card — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// A self-adapting card that restyles based on its OWN inline width rather than\n// the viewport. The same markup renders stacked (media above text) when it sits\n// in a narrow column or sidebar, and side-by-side (media beside text) when it\n// has room — so one component drops into a sidebar, a wide content column, or a\n// grid cell with no per-call breakpoints. Pure CSS; zero JavaScript.\n//\n// Built on (read before editing):\n//   - CSS `container-type: inline-size` — establishes the card as a size query\n//     container so its descendants can be styled against the card's own inline\n//     width (computed in isolation, with inline-size containment to avoid query\n//     loops). We name the container so the threshold variant targets THIS card\n//     and not some ancestor container.\n//       repos/mdn/files/en-us/web/css/reference/properties/container-type/index.md\n//   - The web.dev \"Container query card\" pattern — base styles are single\n//     column / centred; an `@container (min-width: …)` rule flips to a\n//     two-column grid and reveals the description at wider container sizes.\n//       repos/web.dev/src/site/content/en/patterns/layout/container-query-card/index.md\n//       repos/web.dev/src/site/content/en/patterns/layout/container-query-card/assets/style.css\n//\n// shadcn/ui's Card is a static container with no self-adapting behaviour, so\n// there is no upstream class string to mirror 1:1 — we keep the same visual\n// shell (rounded border, bg-card, shadow) as registry/ui/card.tsx and add the\n// container-query layout.\n//   Card shell mirrored from: registry/ui/card.tsx\n//\n// Tailwind v4 container queries (verified against the engine):\n//   - `@container/container-card` → container-type: inline-size + container-name\n//     (repos/tailwindcss/packages/tailwindcss/src/utilities.ts: the `@container`\n//      functional utility emits `container-type` and, with a modifier, the\n//      `container-name`).\n//   - `@min-[28rem]/container-card:<util>` → wraps the utility in\n//     `@container container-card (min-width: 28rem)` so it only fires when THIS\n//     named card is at least the threshold wide\n//     (repos/tailwindcss/packages/tailwindcss/src/variants.ts: the `@container`\n//      variant supports an optional name then the size query).\n//\n// The threshold is published as the `--container-card-break` custom property so\n// it is documented/inspectable, but the actual query lives in the arbitrary\n// `@min-[…]` variant (container queries can't read a custom property in the\n// query condition itself — that is a platform limitation, not a hack).\n\ntype ContainerCardAs = \"article\" | \"section\" | \"div\" | \"li\" | \"aside\"\n\n// The query-container root. `@container/container-card` is the whole point:\n// container-type: inline-size + the name `container-card`. The visual shell\n// matches registry/ui/card.tsx (rounded, bordered, bg-card, shadow).\nconst ROOT =\n  \"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm\"\n\n// The layout block. Stacked by default (flex column). At >= the break width of\n// THIS named container, it becomes a two-column grid with the media beside the\n// body — matching the web.dev pattern's `display: grid; grid-template-columns:\n// 40% 1fr` flip.\nconst LAYOUT =\n  \"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\"\n\n// Media slot: full-bleed banner when stacked; locked column when side-by-side.\nconst MEDIA =\n  \"bg-muted aspect-video w-full @min-[28rem]/container-card:aspect-auto @min-[28rem]/container-card:h-full\"\n\n// Body: generous padding; centred text when stacked, left-aligned when wide\n// (mirrors the pattern's `text-align: center` → `left` flip).\nconst BODY =\n  \"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\"\n\nconst TITLE = \"leading-none font-semibold\"\nconst DESCRIPTION = \"text-sm text-muted-foreground\"\n// Footer actions: centred when stacked, pushed to the start when side-by-side.\nconst FOOTER =\n  \"mt-2 flex items-center justify-center gap-2 @min-[28rem]/container-card:justify-start\"\n\ntype ContainerCardProps = PropsWithChildren<{\n  // Semantic element. Defaults to <article> because a container card is almost\n  // always self-contained, syndicatable content (product, post, comment).\n  // See repos/mdn/files/en-us/web/html/reference/elements/article/index.md\n  as?: ContainerCardAs\n  // The media child (img / video / picture / div). Rendered in the media slot\n  // ABOVE the body when stacked, BESIDE it when wide. Omit for a text-only card.\n  media?: Child\n  // Inline width at which the card flips from stacked to side-by-side. Any CSS\n  // length the @container query understands. Published as the\n  // --container-card-break custom property for inspection. Note: changing the\n  // numeric threshold requires editing the @min-[…] variant too, since a\n  // container query condition cannot read a custom property (platform limit).\n  break?: string\n  ariaLabel?: string\n  // Pair with the id of the title inside so the <article>/<section> has an\n  // accessible name for AT landmark navigation.\n  ariaLabelledby?: string\n  class?: ClassValue\n  id?: string\n  // Forward hx-*, data-*, aria-*, and standard attributes onto the root.\n  [key: string]: unknown\n}>\n\nexport function ContainerCard(props: ContainerCardProps) {\n  const {\n    as = \"article\",\n    media,\n    break: breakAt = \"28rem\",\n    ariaLabel,\n    ariaLabelledby,\n    class: className,\n    id,\n    children,\n    ...rest\n  } = props as ContainerCardProps\n  const Tag: any = as\n  return (\n    <Tag\n      id={id}\n      data-slot=\"container-card\"\n      // Documented threshold; the active query lives in the @min-[28rem] variant.\n      style={`--container-card-break:${breakAt}`}\n      aria-label={ariaLabel}\n      aria-labelledby={ariaLabelledby}\n      class={cn(ROOT, className)}\n      {...rest}\n    >\n      <div data-slot=\"container-card-layout\" class={LAYOUT}>\n        {media ? (\n          <div data-slot=\"container-card-media\" class={MEDIA}>\n            {media}\n          </div>\n        ) : null}\n        <div data-slot=\"container-card-body\" class={BODY}>\n          {children}\n        </div>\n      </div>\n    </Tag>\n  )\n}\n\nexport function ContainerCardTitle(\n  props: PropsWithChildren<{ class?: ClassValue; id?: string }>,\n) {\n  return (\n    <div data-slot=\"container-card-title\" id={props.id} class={cn(TITLE, props.class)}>\n      {props.children}\n    </div>\n  )\n}\n\nexport function ContainerCardDescription(\n  props: PropsWithChildren<{ class?: ClassValue }>,\n) {\n  return (\n    <p data-slot=\"container-card-description\" class={cn(DESCRIPTION, props.class)}>\n      {props.children}\n    </p>\n  )\n}\n\nexport function ContainerCardFooter(\n  props: PropsWithChildren<{ class?: ClassValue }>,\n) {\n  return (\n    <div data-slot=\"container-card-footer\" class={cn(FOOTER, props.class)}>\n      {props.children}\n    </div>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/container-card.html",
      "type": "registry:file",
      "target": "templates/components/container-card.html",
      "content": "{# Container Card macro — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/container-card.tsx.\n\n   A self-adapting card that restyles based on its OWN inline width: stacked\n   (media above text) when narrow, side-by-side when wide. Built on CSS\n   container queries (container-type: inline-size + @container). Pure CSS; no JS.\n\n     repos/mdn/files/en-us/web/css/reference/properties/container-type/index.md\n     repos/web.dev/src/site/content/en/patterns/layout/container-query-card/index.md\n\n   Usage:\n     {% from \"components/container-card.html\" import container_card %}\n\n     {% call container_card(\n          title=\"Card title\",\n          description=\"Supporting copy\",\n          media='<img src=\"/cover.jpg\" alt=\"\" class=\"size-full object-cover\">'\n        ) %}\n       <a href=\"/more\" class=\"text-sm font-medium text-primary underline-offset-4 hover:underline\">Read more</a>\n     {% endcall %}\n\n   Args:\n     tag             article | section | div | li | aside. Default \"article\".\n     title           card title text (optional; rendered as the title slot).\n     title_id        id on the title, pair with aria_labelledby for naming.\n     description     supporting copy (optional).\n     media           raw HTML for the media slot (img/video/picture). Optional.\n     break_at        inline width at which the card flips to side-by-side.\n                     Published as --container-card-break. Default \"28rem\".\n                     (Changing the number also needs the @min-[…] variant edited.)\n     aria_label / aria_labelledby   accessible name for the root.\n     extra_class     extra classes appended to the root.\n     attrs           dict of extra attributes (hx-*, data-*, aria-*).\n   The caller() body becomes the footer / actions row. #}\n\n{% macro container_card(\n    tag=\"article\",\n    title=none,\n    title_id=none,\n    description=none,\n    media=none,\n    break_at=\"28rem\",\n    aria_label=none,\n    aria_labelledby=none,\n    extra_class=\"\",\n    attrs={}\n) %}\n<{{ tag }}\n  data-slot=\"container-card\"\n  style=\"--container-card-break:{{ break_at }}\"\n  {%- if aria_label %} aria-label=\"{{ aria_label }}\"{% endif %}\n  {%- if aria_labelledby %} aria-labelledby=\"{{ aria_labelledby }}\"{% endif %}\n  {%- for k, v in attrs.items() %} {{ k|replace('_','-') }}=\"{{ v }}\"{% endfor %}\n  class=\"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm {{ extra_class }}\">\n  <div data-slot=\"container-card-layout\" class=\"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\">\n    {%- if media %}\n    <div data-slot=\"container-card-media\" class=\"bg-muted aspect-video w-full @min-[28rem]/container-card:aspect-auto @min-[28rem]/container-card:h-full\">{{ media|safe }}</div>\n    {%- endif %}\n    <div data-slot=\"container-card-body\" class=\"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\">\n      {%- if title %}\n      <div data-slot=\"container-card-title\"{% if title_id %} id=\"{{ title_id }}\"{% endif %} class=\"leading-none font-semibold\">{{ title }}</div>\n      {%- endif %}\n      {%- if description %}\n      <p data-slot=\"container-card-description\" class=\"text-sm text-muted-foreground\">{{ description }}</p>\n      {%- endif %}\n      <div data-slot=\"container-card-footer\" class=\"mt-2 flex items-center justify-center gap-2 @min-[28rem]/container-card:justify-start\">{{ caller() }}</div>\n    </div>\n  </div>\n</{{ tag }}>\n{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/container-card.tmpl",
      "type": "registry:file",
      "target": "components/container-card.tmpl",
      "content": "{{/*\n  Container Card template — shadcn-htmx, htmx v4 + Tailwind v4.\n  Mirrors registry/ui/container-card.tsx.\n\n  A self-adapting card that restyles based on its OWN inline width: stacked\n  (media above text) when narrow, side-by-side when wide. Built on CSS\n  container queries (container-type: inline-size + @container). Pure CSS; no JS.\n\n    repos/mdn/files/en-us/web/css/reference/properties/container-type/index.md\n    repos/web.dev/src/site/content/en/patterns/layout/container-query-card/index.md\n\n      type ContainerCardArgs struct {\n          Tag         string // article | section | div | li | aside; default \"article\"\n          Title       string // title slot text (optional)\n          TitleID     string // id on the title (pair with AriaLabelledby)\n          Description string // supporting copy (optional)\n          Media       string // raw HTML for the media slot (use htmlSafe); optional\n          Break       string // flip threshold; default \"28rem\" (also edit @min-[…])\n          AriaLabel   string\n          AriaLabelledby string\n          Class       string // extra classes appended to the root\n          Body        string // footer / actions HTML (use htmlSafe)\n      }\n\n  Usage:\n      {{template \"container-card\" (dict\n          \"Title\" \"Card title\"\n          \"Description\" \"Supporting copy\"\n          \"Media\" (htmlSafe `<img src=\"/cover.jpg\" alt=\"\" class=\"size-full object-cover\">`)\n          \"Body\" (htmlSafe `<a href=\"/more\">Read more</a>`))}}\n*/}}\n\n{{define \"container-card\"}}\n{{- $tag := or .Tag \"article\" -}}\n{{- $break := or .Break \"28rem\" -}}\n<{{$tag}} data-slot=\"container-card\" style=\"--container-card-break:{{$break}}\"{{if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{end}}{{if .AriaLabelledby}} aria-labelledby=\"{{.AriaLabelledby}}\"{{end}} class=\"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm{{if .Class}} {{.Class}}{{end}}\">\n  <div data-slot=\"container-card-layout\" class=\"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\">\n    {{- if .Media}}\n    <div data-slot=\"container-card-media\" class=\"bg-muted aspect-video w-full @min-[28rem]/container-card:aspect-auto @min-[28rem]/container-card:h-full\">{{htmlSafe .Media}}</div>\n    {{- end}}\n    <div data-slot=\"container-card-body\" class=\"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\">\n      {{- if .Title}}\n      <div data-slot=\"container-card-title\"{{if .TitleID}} id=\"{{.TitleID}}\"{{end}} class=\"leading-none font-semibold\">{{.Title}}</div>\n      {{- end}}\n      {{- if .Description}}\n      <p data-slot=\"container-card-description\" class=\"text-sm text-muted-foreground\">{{.Description}}</p>\n      {{- end}}\n      <div data-slot=\"container-card-footer\" class=\"mt-2 flex items-center justify-center gap-2 @min-[28rem]/container-card:justify-start\">{{htmlSafe .Body}}</div>\n    </div>\n  </div>\n</{{$tag}}>\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/container_card.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/container_card.ex",
      "content": "defmodule ShadcnHtmx.Components.ContainerCard do\n  @moduledoc \"\"\"\n  Container Card — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Mirrors registry/ui/container-card.tsx.\n\n  A self-adapting card that restyles based on its OWN inline width rather than\n  the viewport: stacked (media above text) when narrow, side-by-side when wide.\n  The same markup drops into a sidebar, a wide column, or a grid cell with no\n  per-call breakpoints. Built on CSS container queries\n  (container-type: inline-size + @container). Pure CSS; zero JavaScript.\n\n    - repos/mdn/files/en-us/web/css/reference/properties/container-type/index.md\n    - repos/web.dev/src/site/content/en/patterns/layout/container-query-card/index.md\n\n  ## Examples\n\n      <.container_card>\n        <:media>\n          <img src=\"/cover.jpg\" alt=\"\" class=\"size-full object-cover\" />\n        </:media>\n        <.container_card_title>Card title</.container_card_title>\n        <.container_card_description>Supporting copy.</.container_card_description>\n        <.container_card_footer>\n          <a href=\"/more\">Read more</a>\n        </.container_card_footer>\n      </.container_card>\n  \"\"\"\n\n  use Phoenix.Component\n\n  attr :tag, :string, default: \"article\", values: ~w(article section div li aside)\n  # Flip threshold; published as --container-card-break. Changing the number\n  # also requires editing the @min-[…] variant (a container query condition\n  # cannot read a custom property — platform limitation, not a hack).\n  attr :break, :string, default: \"28rem\"\n  attr :class, :string, default: nil\n  attr :rest, :global, include: ~w(aria-label aria-labelledby)\n  slot :media\n  slot :inner_block, required: true\n\n  def container_card(assigns) do\n    ~H\"\"\"\n    <.dynamic_tag\n      tag_name={@tag}\n      data-slot=\"container-card\"\n      style={\"--container-card-break:#{@break}\"}\n      class={[\n        \"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm\",\n        @class\n      ]}\n      {@rest}\n    >\n      <div\n        data-slot=\"container-card-layout\"\n        class=\"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\"\n      >\n        <div\n          :if={@media != []}\n          data-slot=\"container-card-media\"\n          class=\"bg-muted aspect-video w-full @min-[28rem]/container-card:aspect-auto @min-[28rem]/container-card:h-full\"\n        >\n          {render_slot(@media)}\n        </div>\n        <div\n          data-slot=\"container-card-body\"\n          class=\"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\"\n        >\n          {render_slot(@inner_block)}\n        </div>\n      </div>\n    </.dynamic_tag>\n    \"\"\"\n  end\n\n  attr :id, :string, default: nil\n  attr :class, :string, default: nil\n  slot :inner_block, required: true\n\n  def container_card_title(assigns) do\n    ~H\"\"\"\n    <div data-slot=\"container-card-title\" id={@id} class={[\"leading-none font-semibold\", @class]}>\n      {render_slot(@inner_block)}\n    </div>\n    \"\"\"\n  end\n\n  attr :class, :string, default: nil\n  slot :inner_block, required: true\n\n  def container_card_description(assigns) do\n    ~H\"\"\"\n    <p data-slot=\"container-card-description\" class={[\"text-sm text-muted-foreground\", @class]}>\n      {render_slot(@inner_block)}\n    </p>\n    \"\"\"\n  end\n\n  attr :class, :string, default: nil\n  slot :inner_block, required: true\n\n  def container_card_footer(assigns) do\n    ~H\"\"\"\n    <div\n      data-slot=\"container-card-footer\"\n      class={[\n        \"mt-2 flex items-center justify-center gap-2 @min-[28rem]/container-card:justify-start\",\n        @class\n      ]}\n    >\n      {render_slot(@inner_block)}\n    </div>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/container-card.html",
      "type": "registry:file",
      "target": "snippets/container-card.html",
      "content": "<!--\n  shadcn-htmx — raw HTML Container Card snippet.\n  Mirrors registry/ui/container-card.tsx.\n\n  A self-adapting card that restyles based on its OWN inline width: stacked\n  (media above text) when narrow, side-by-side when wide. Built on CSS\n  container queries (container-type: inline-size + @container). Pure CSS;\n  no script. Relies only on theme tokens.\n\n  How it works:\n    - The root carries `@container/container-card`, which sets\n      `container-type: inline-size` and names the container `container-card`.\n    - Descendants use `@min-[28rem]/container-card:` variants, which only fire\n      when THIS named card is at least 28rem wide — independent of the viewport.\n    - The same markup therefore adapts to whatever column it lands in.\n\n    repos/mdn/files/en-us/web/css/reference/properties/container-type/index.md\n    repos/web.dev/src/site/content/en/patterns/layout/container-query-card/index.md\n-->\n\n<!-- Card with media: stacked under ~28rem, media-beside-text at or above it. -->\n<article data-slot=\"container-card\"\n         style=\"--container-card-break:28rem\"\n         aria-labelledby=\"cc-title\"\n         class=\"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm\">\n  <div data-slot=\"container-card-layout\"\n       class=\"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\">\n    <div data-slot=\"container-card-media\"\n         class=\"bg-muted aspect-video w-full @min-[28rem]/container-card:aspect-auto @min-[28rem]/container-card:h-full\">\n      <img src=\"/cover.jpg\" alt=\"\" class=\"size-full object-cover\" />\n    </div>\n    <div data-slot=\"container-card-body\"\n         class=\"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\">\n      <div data-slot=\"container-card-title\" id=\"cc-title\" class=\"leading-none font-semibold\">Card title</div>\n      <p data-slot=\"container-card-description\" class=\"text-sm text-muted-foreground\">\n        Supporting copy that adapts with the card's own width.\n      </p>\n      <div data-slot=\"container-card-footer\"\n           class=\"mt-2 flex items-center justify-center gap-2 @min-[28rem]/container-card:justify-start\">\n        <a href=\"/more\" class=\"text-sm font-medium text-primary underline-offset-4 hover:underline\">Read more</a>\n      </div>\n    </div>\n  </div>\n</article>\n\n<!-- Text-only card: omit the media slot. Still adapts its alignment by width. -->\n<article data-slot=\"container-card\"\n         style=\"--container-card-break:28rem\"\n         class=\"@container/container-card overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm\">\n  <div data-slot=\"container-card-layout\"\n       class=\"flex flex-col @min-[28rem]/container-card:grid @min-[28rem]/container-card:grid-cols-[40%_1fr] @min-[28rem]/container-card:items-stretch\">\n    <div data-slot=\"container-card-body\"\n         class=\"flex flex-col gap-2 p-6 text-center @min-[28rem]/container-card:text-left\">\n      <div data-slot=\"container-card-title\" class=\"leading-none font-semibold\">No media</div>\n      <p data-slot=\"container-card-description\" class=\"text-sm text-muted-foreground\">\n        Works without a media slot too.\n      </p>\n    </div>\n  </div>\n</article>\n"
    }
  ]
}
