{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card",
  "type": "registry:ui",
  "title": "Card",
  "description": "A raised surface for one object that links somewhere. A linked card is one real link stretched over the whole card, and hover moves the fill one step, with no lift, growth or scale.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/card.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/card 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Card: a raised surface for one independent object that links somewhere, such as a client site, a\n * build or an invoice. It is a few small parts you compose: Card, CardCover, CardBody, CardTitle,\n * CardDescription and CardLink.\n *\n * - Flat: the float shadow in place of the hairline (a bleed cover loses its stand-in hairline too); the inset cover stays concentric.\n * - Surface (components.md, Card): `.litt-raised`. The surface radius, surface-1, an inset hairline and the\n *   top highlight, and no outer shadow (R3). Never glass (R3), never an accent rail.\n * - Padding follows the cover. A plain card pads space-6. With an inset cover the card pads space-1 (4px),\n *   the cover takes the control radius (4 + 4 = 8, so the corners nest) and CardBody pads space-4 above and\n *   at the sides, space-3 below. With a bleed cover the card pads nothing, the cover runs to the edges and\n *   takes the card's surface radius at the top, and CardBody pads space-6. Card tells its parts which one it is.\n * - A linked card holds one real link or button (CardLink), stretched over the whole card by its ::after.\n *   That keeps the semantics right (the title is the accessible name, the card is one tab stop) without\n *   wrapping block content in an <a>. Anything else clickable inside the card sits above it with `relative`.\n * - Hover (instant tier, color only): a linked card's fill moves one step, surface-1 to surface-2, and its\n *   cover steps to surface-3 so it keeps its edge. No lift, no shadow growth, no scale; the box never moves.\n *   An unlinked card has no hover. Touch loses nothing, because hover reveals nothing.\n * - Focus by modality: :focus-visible on the link draws the 2px focus ring around the whole card, at a 2px\n *   offset, and the link's own outline is removed so the two never stack.\n * - Light: `lit` adds `.litt-lit .litt-grain` as the card's own top light. It is optional and static (R2).\n *   The page already has one light source (R2), so use it on one card, not on a set.\n * - Motion: none of its own. The entrance cascade for a set of cards belongs to the page (see List and cards).\n */\nimport { createContext, useContext, type ComponentProps, type HTMLAttributes, type ReactNode } from \"react\";\n\n/** How a cover sits in its card. \"none\" is a card with no cover. */\ntype CoverStyle = \"none\" | \"inset\" | \"bleed\";\nconst CoverContext = createContext<CoverStyle>(\"none\");\n\n/* -------------------------------------------------------------------------- */\n/* Card                                                                       */\n/* -------------------------------------------------------------------------- */\n\nconst cardPadding: Record<CoverStyle, string> = {\n  none: \"p-6\",\n  inset: \"p-1\",\n  bleed: \"p-0\",\n};\n\nconst card =\n  \"litt-raised group/card relative flex min-w-0 flex-col rounded-litt-surface in-[[data-material=flat]]:shadow-litt-float \" +\n  \"transition-[background-color] duration-(--litt-dur-instant) ease-litt \" +\n  \"has-[[data-card-link]]:hover:bg-litt-surface-2\";\n\nexport type CardProps = HTMLAttributes<HTMLElement> & {\n  /** The element. Use li when the card sits in a list or grid of ul, and keep article when it stands alone. */\n  as?: \"article\" | \"div\" | \"li\" | \"section\";\n  /** How a CardCover sits: inset pads the card by 4px and gives the cover the control radius, bleed runs it to the card's edges. Leave it out for a card with no cover. */\n  cover?: \"inset\" | \"bleed\";\n  /** Adds the card's own light: a soft accent falloff from the top, with a trace of grain. Keep it to one card per page. */\n  lit?: boolean;\n  /** The card's parts: a CardCover, then a CardBody with a CardTitle and a CardDescription. */\n  children?: ReactNode;\n  /** Extra classes for the card, such as a max width. */\n  className?: string;\n};\n\n/** A raised card. A linked card holds a CardLink; its whole surface is the target and its fill steps on hover. */\nexport function Card({ as: Tag = \"article\", cover, lit = false, className = \"\", children, ...rest }: CardProps) {\n  const style: CoverStyle = cover ?? \"none\";\n  return (\n    <CoverContext.Provider value={style}>\n      <Tag className={[card, cardPadding[style], lit ? \"litt-lit litt-grain\" : \"\", className].join(\" \")} {...rest}>\n        {children}\n      </Tag>\n    </CoverContext.Provider>\n  );\n}\n\n/* -------------------------------------------------------------------------- */\n/* CardCover                                                                  */\n/* -------------------------------------------------------------------------- */\n\ntype Ratio = \"8/5\" | \"16/9\" | \"4/3\" | \"1/1\" | \"auto\";\nconst ratios: Record<Ratio, string> = {\n  \"8/5\": \"aspect-[8/5]\",\n  \"16/9\": \"aspect-video\",\n  \"4/3\": \"aspect-[4/3]\",\n  \"1/1\": \"aspect-square\",\n  auto: \"\",\n};\n\nconst coverShape: Record<CoverStyle, string> = {\n  // Padded by 4px, so the surface radius less 4px sits concentric inside the card's: the control radius at every\n  // shape but the pill, where the control radius would be rounder than the card.\n  none: \"rounded-[calc(var(--litt-radius-surface)-4px)]\",\n  inset: \"rounded-[calc(var(--litt-radius-surface)-4px)]\",\n  // Full-bleed: it takes the card's surface radius at the top. A hairline drawn over it\n  // (::before) stands in for the card's own, which the cover would otherwise hide.\n  bleed:\n    \"rounded-t-litt-surface before:pointer-events-none before:absolute before:inset-0 before:z-10 \" +\n    \"before:rounded-t-litt-surface before:shadow-[inset_0_0_0_1px_var(--litt-line)] in-[[data-material=flat]]:before:shadow-none\",\n};\n\nexport type CardCoverProps = {\n  /** An image with alt text, or decorative art that is aria-hidden. It fills the cover. */\n  children?: ReactNode;\n  /** The cover's shape: 8/5 (the default), 16/9, 4/3, 1/1, or auto to let the content set the height. */\n  ratio?: Ratio;\n  /** Adds the cover's own light and grain, as a lit hero would have. The art draws over it, so it shows on an empty or transparent cover. */\n  lit?: boolean;\n  /** Extra classes for the cover. */\n  className?: string;\n};\n\n/** The picture at the top of a card. It fills surface-2, and steps to surface-3 when its linked card is hovered. */\nexport function CardCover({ children, ratio = \"8/5\", lit = false, className = \"\" }: CardCoverProps) {\n  const style = useContext(CoverContext);\n  return (\n    <div\n      data-card-cover=\"\"\n      className={[\n        \"relative shrink-0 overflow-hidden bg-litt-surface-2\",\n        \"transition-[background-color] duration-(--litt-dur-instant) ease-litt\",\n        \"group-has-[[data-card-link]]/card:group-hover/card:bg-litt-surface-3\",\n        coverShape[style],\n        ratios[ratio],\n        lit ? \"litt-lit litt-grain\" : \"\",\n        className,\n      ].join(\" \")}\n    >\n      {children}\n    </div>\n  );\n}\n\n/* -------------------------------------------------------------------------- */\n/* CardBody, CardTitle, CardDescription                                       */\n/* -------------------------------------------------------------------------- */\n\nconst bodyPadding: Record<CoverStyle, string> = {\n  // The card pads space-6 itself.\n  none: \"\",\n  // space-4 above and at the sides, space-3 below (components.md).\n  inset: \"px-4 pt-4 pb-3\",\n  bleed: \"p-6\",\n};\n\nexport type CardBodyProps = {\n  /** The text and anything under it. Put the title and description first; a row of tags can follow with `mt-4`, or `mt-auto` to sit at the bottom of a taller card. */\n  children?: ReactNode;\n  /** Extra classes for the body. */\n  className?: string;\n};\n\n/** The padded text area under a cover, or the whole card when it has no cover. It grows to fill a taller card. */\nexport function CardBody({ children, className = \"\" }: CardBodyProps) {\n  const style = useContext(CoverContext);\n  return <div className={[\"flex flex-1 flex-col gap-1\", bodyPadding[style], className].join(\" \")}>{children}</div>;\n}\n\nexport type CardTitleProps = {\n  /** The heading level. Pick the one that fits the page's outline; it changes the semantics only, never the size. */\n  as?: \"h2\" | \"h3\" | \"h4\";\n  /** The title text. Wrap it in a CardLink to make the card a link. */\n  children: ReactNode;\n  /** Extra classes for the title, such as truncate. */\n  className?: string;\n};\n\n/** The card's name, in body size and fg. A real heading, so a screen reader can list the cards. */\nexport function CardTitle({ as: Tag = \"h3\", children, className = \"\" }: CardTitleProps) {\n  return <Tag className={\"text-litt-body font-litt-title text-litt-fg \" + className}>{children}</Tag>;\n}\n\nexport type CardDescriptionProps = {\n  /** One or two short lines about the card. */\n  children: ReactNode;\n  /** Extra classes for the description, such as truncate. */\n  className?: string;\n};\n\n/** Supporting text in small size and fg-subtle. */\nexport function CardDescription({ children, className = \"\" }: CardDescriptionProps) {\n  return <p className={\"text-litt-small text-litt-fg-subtle \" + className}>{children}</p>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* CardLink                                                                   */\n/* -------------------------------------------------------------------------- */\n\nconst link =\n  \"cursor-pointer text-left outline-none focus-visible:outline-none \" +\n  // The stretched target, and the focus ring on the whole card (not on the link's own text).\n  \"after:absolute after:inset-0 after:rounded-litt-surface \" +\n  \"focus-visible:after:outline-2 focus-visible:after:outline-offset-2 focus-visible:after:outline-litt-focus\";\n\nexport type CardLinkProps = Omit<ComponentProps<\"a\">, \"ref\" | \"href\"> & {\n  /** Where the card goes. Leave it out and CardLink renders a button, so use onClick for the action. */\n  href?: string;\n};\n\n/**\n * The one link or button that makes a card clickable. Put it around the title text. Its ::after covers the\n * whole card, so the card is the target and the title is the name. It must sit inside a Card.\n */\nexport function CardLink({ href, className = \"\", children, ...rest }: CardLinkProps) {\n  if (href !== undefined) {\n    return (\n      <a href={href} data-card-link=\"\" className={link + \" \" + className} {...rest}>\n        {children}\n      </a>\n    );\n  }\n  return (\n    <button\n      type=\"button\"\n      data-card-link=\"\"\n      className={link + \" \" + className}\n      {...(rest as ComponentProps<\"button\">)}\n    >\n      {children}\n    </button>\n  );\n}\n",
      "target": "components/litt/card.tsx"
    }
  ],
  "docs": "Import Card, CardCover, CardBody, CardTitle, CardDescription and CardLink from components/litt/card. Put CardLink around the title to make the card a link. Anything else you can press inside it needs relative.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
