{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tag",
  "type": "registry:ui",
  "title": "Tag",
  "description": "A small label at the control radius that names a thing or reports its state. A status tag colors its text, never the fill, and always says the status in words.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json",
    "https://littmd.vercel.app/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/tag.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/tag 2026.11.09. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Tag: a small label that names something (Framer, Site), files it under a category (Finance),\n * or reports its status (Live, Review).\n *\n * - Press (a small block): 24px tall at the control radius, a --litt-block with --litt-block-lift (its lit top edge and a short\n *   cast, no drawn line), and the sans at the caption size and the label weight (R8, v0.12), in sentence case, the word\n *   on its face. A plain tag is fg-muted.\n * - A status tag colors its text with the status color, never the fill, and there is no dot.\n *   The label says the status in words, so color never carries it alone.\n * - The status set is the tokens' own: success, warning and danger, plus neutral (fg-subtle).\n *   There is no accent status: the accent is chosen per project, and it does not promise\n *   4.5:1 for caption text.\n * - A hue tag (R7, v0.11) files the thing under a category, so its color carries data: the\n *   hue's tint as the fill, its -text tone for the label (4.5:1 on the tint, checked by\n *   check.mjs), and a hairline of the hue under the top highlight. One hue means one category\n *   in a view. A hue is never a status: a tag that says how something stands takes `status`.\n * - Raised (R3): a value you can lift out of a field (Combobox's picks) is\n *   the press material's block, --litt-block with --litt-block-lift, in --litt-fg like the text typed beside it.\n *   It sits inside the field's control radius, so its corners are that radius less 2px. Forced\n *   colors drops the lift, so it keeps a 1px border. A hue tag files a category and never rises.\n * - Flat, bodies/tag.flat.tsx (proposals/flat-pill.md): the word in a grey pill (--litt-surface-3), nothing raised and no\n *   hairline. A hue tag keeps its tint as a pill, and a raised tag is a --litt-surface-1 pill, so it stands out of the grey\n *   field it sits in.\n * - One contract, three bodies (proposals/worlds.md, Bodies). This file is the contract: the props and the plain span with\n *   its label. It resolves them once into TagBodyProps and renders the body the material picks (byWorld). PressBody, below,\n *   is the default; bodies/tag.flat.tsx and bodies/tag.device.tsx are the other two, each drawing its own span with data-body.\n * - Device, bodies/tag.device.tsx: the word engraved in capitals on a small plate; a status tag adds a lamp in its status\n *   color, and the word still says the status.\n * - Static: no motion and no states. A tag is a label, not a control, so it never takes\n *   focus. If you need something to press, use Button or a chip.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { byWorld, useMaterial } from \"../../lib/litt/motion\";\nimport { TagDeviceBody } from \"./bodies/tag.device\";\nimport { TagFlatBody } from \"./bodies/tag.flat\";\n\nexport type TagStatus = \"success\" | \"warning\" | \"danger\" | \"neutral\";\n\n/** The five data hues, in their fixed order (R7). */\nexport type TagHue = \"blue\" | \"amber\" | \"teal\" | \"violet\" | \"rose\";\n\nconst STATUS: Record<TagStatus, string> = {\n  success: \"text-litt-success\",\n  warning: \"text-litt-warning\",\n  danger: \"text-litt-danger\",\n  neutral: \"text-litt-fg-subtle\",\n};\n\nconst HUE: Record<TagHue, string> = {\n  blue: \"bg-litt-data-blue-tint text-litt-data-blue-text shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--litt-data-blue)_24%,transparent),var(--litt-highlight)]\",\n  amber: \"bg-litt-data-amber-tint text-litt-data-amber-text shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--litt-data-amber)_24%,transparent),var(--litt-highlight)]\",\n  teal: \"bg-litt-data-teal-tint text-litt-data-teal-text shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--litt-data-teal)_24%,transparent),var(--litt-highlight)]\",\n  violet: \"bg-litt-data-violet-tint text-litt-data-violet-text shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--litt-data-violet)_24%,transparent),var(--litt-highlight)]\",\n  rose: \"bg-litt-data-rose-tint text-litt-data-rose-text shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--litt-data-rose)_24%,transparent),var(--litt-highlight)]\",\n};\n\nconst PLAIN = \"rounded-litt-control bg-litt-block shadow-(--litt-block-lift) \";\n/* In a field, 4px in: the field's radius less the inset, never under 2px (foundations.md, Concentric). */\nconst RAISED = \"rounded-[max(calc(var(--litt-radius-control)-4px),min(calc(var(--litt-radius-control)-2px),2px))] bg-litt-block shadow-(--litt-block-lift) forced-colors:border \";\n\nexport type TagProps = HTMLAttributes<HTMLSpanElement> & {\n  /** Leave it out for a plain label. Set it for a status: the text takes that color, and the fill never does. */\n  status?: TagStatus;\n  /** Files the thing under a category with one of the five data hues: a tinted fill and the hue's text tone. Use one hue per category in a view, and never for a status. */\n  hue?: TagHue;\n  /** Draws a plain tag as a raised block, for a tag you can lift out of a field: Combobox uses it for its picks. Ignored with a hue. */\n  raised?: boolean;\n  /** The label. Keep it to a word or two, because a tag never wraps. */\n  children: ReactNode;\n  /** Extra classes for the tag. */\n  className?: string;\n};\n\n/**\n * What the contract hands a body: the tag resolved once. A body draws the span and keeps the label inside it as given\n * (`label`), says a status in words (the lamp is decoration, aria-hidden), stays 24px tall, takes no focus, and\n * carries data-body on the span.\n */\nexport type TagBodyProps = {\n  status?: TagStatus;\n  hue?: TagHue;\n  raised: boolean;\n  /** The label, truncated when it is plain text. */\n  label: ReactNode;\n  /** The span's other props. */\n  rest: HTMLAttributes<HTMLSpanElement>;\n  className: string;\n};\n\nconst BASE = \"inline-flex h-6 max-w-full shrink-0 items-center whitespace-nowrap px-2 text-litt-caption \";\n\n/** Press, the block: today's chip (the flat step), or the raised block for a value in a field. */\nfunction PressBody({ status, hue, raised, label, rest, className }: TagBodyProps) {\n  const ink = status ? STATUS[status] : raised ? \"text-litt-fg\" : \"text-litt-fg-muted\";\n  return (\n    <span\n      data-body=\"press\"\n      className={BASE + (hue ? \"rounded-litt-control \" + HUE[hue] : (raised ? RAISED : PLAIN) + ink) + \" \" + className}\n      {...rest}\n    >\n      {label}\n    </span>\n  );\n}\n\n/** A 24px label at the control radius, in the sans at the label weight. Add `status` to color its text, `hue` to file it under a category, or `raised` for a value in a field. */\nexport function Tag({ status, hue, raised = false, children, className = \"\", ...rest }: TagProps) {\n  const Body = byWorld(useMaterial(), { press: PressBody, flat: TagFlatBody, device: TagDeviceBody });\n  const label = typeof children === \"string\" || typeof children === \"number\" ? <span className=\"min-w-0 truncate\">{children}</span> : children;\n  return <Body status={status} hue={hue} raised={raised} label={label} rest={rest} className={className} />;\n}\n",
      "target": "components/litt/tag.tsx"
    },
    {
      "path": "registry/litt/ui/bodies/tag.device.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/tag 2026.11.09. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Tag, the Device body (proposals/worlds.md, Bodies; material.md, 8): the word engraved in capitals on a small plate.\n *\n * - A plate (--litt-block with --litt-plate-lift) at the control radius, 24px tall, the word in --litt-fg-muted as a legend\n *   (litt-legend: --litt-label-case, --litt-label-tracking, --litt-engrave). A hue tag keeps its tint and text tone on the plate.\n * - A status tag adds a lamp (size-1.5, fully round) in its status color before the word, at mid-height and 4px\n *   further in so it stays clear of the curve at Pill. The word still says the status (R5); the lamp is aria-hidden.\n * - A raised tag (a value in a field) is a small key: --litt-block with --litt-key-lift, the word in --litt-fg.\n * - Static: nothing moves. Forced colors drops shadows, so the plate and the key keep a border.\n */\nimport type { TagBodyProps, TagHue, TagStatus } from \"../tag\";\n\nconst LAMP: Record<TagStatus, string> = {\n  success: \"bg-litt-success\",\n  warning: \"bg-litt-warning\",\n  danger: \"bg-litt-danger\",\n  neutral: \"bg-litt-field-line\",\n};\n\nconst HUE: Record<TagHue, string> = {\n  blue: \"bg-litt-data-blue-tint text-litt-data-blue-text\",\n  amber: \"bg-litt-data-amber-tint text-litt-data-amber-text\",\n  teal: \"bg-litt-data-teal-tint text-litt-data-teal-text\",\n  violet: \"bg-litt-data-violet-tint text-litt-data-violet-text\",\n  rose: \"bg-litt-data-rose-tint text-litt-data-rose-text\",\n};\n\nconst PLATE = \"rounded-litt-control bg-litt-block shadow-(--litt-plate-lift) \";\nconst KEY = \"rounded-[max(calc(var(--litt-radius-control)-4px),min(calc(var(--litt-radius-control)-2px),2px))] bg-litt-block shadow-(--litt-key-lift) \";\n\n/** Device, the engraved plate. */\nexport function TagDeviceBody({ status, hue, raised, label, rest, className }: TagBodyProps) {\n  const surface = hue ? \"rounded-litt-control shadow-(--litt-plate-lift) \" + HUE[hue] : (raised ? KEY : PLATE) + (status || raised ? \"text-litt-fg\" : \"text-litt-fg-muted\");\n  return (\n    <span\n      data-body=\"device\"\n      className={\"inline-flex h-6 max-w-full shrink-0 items-center gap-1.5 whitespace-nowrap text-litt-caption litt-legend forced-colors:border \" + (status ? \"pl-2.5 pr-2 \" : \"px-2 \") + surface + \" \" + className}\n      {...rest}\n    >\n      {status ? <span aria-hidden=\"true\" className={\"size-1.5 shrink-0 rounded-litt-pill forced-colors:bg-[CanvasText] \" + LAMP[status]} /> : null}\n      {label}\n    </span>\n  );\n}\n",
      "target": "components/litt/bodies/tag.device.tsx"
    },
    {
      "path": "registry/litt/ui/bodies/tag.flat.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/tag 2026.11.09. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Tag, the Flat body (proposals/worlds.md, Bodies; material.md, 7): the word in a grey pill, 24px tall, nothing raised.\n * Grey (--litt-surface-3) for a plain tag, the hue's tint for a category, a --litt-surface-1 pill for a value raised out of a\n * field. No hairline and no shadow. A status tag colors its text and the word says the status. Forced colors drops\n * fills, so the pill keeps a border.\n */\nimport type { TagBodyProps, TagHue, TagStatus } from \"../tag\";\n\nconst STATUS: Record<TagStatus, string> = {\n  success: \"text-litt-success\",\n  warning: \"text-litt-warning\",\n  danger: \"text-litt-danger\",\n  neutral: \"text-litt-fg-subtle\",\n};\n\nconst HUE: Record<TagHue, string> = {\n  blue: \"bg-litt-data-blue-tint text-litt-data-blue-text\",\n  amber: \"bg-litt-data-amber-tint text-litt-data-amber-text\",\n  teal: \"bg-litt-data-teal-tint text-litt-data-teal-text\",\n  violet: \"bg-litt-data-violet-tint text-litt-data-violet-text\",\n  rose: \"bg-litt-data-rose-tint text-litt-data-rose-text\",\n};\n\nconst PLAIN = \"rounded-litt-control bg-litt-surface-3 \";\nconst RAISED = \"rounded-[max(calc(var(--litt-radius-control)-4px),min(calc(var(--litt-radius-control)-2px),2px))] bg-litt-surface-1 forced-colors:border \";\n\n/** Flat, the grey chip. */\nexport function TagFlatBody({ status, hue, raised, label, rest, className }: TagBodyProps) {\n  const ink = status ? STATUS[status] : raised ? \"text-litt-fg\" : \"text-litt-fg-muted\";\n  return (\n    <span\n      data-body=\"flat\"\n      className={\"inline-flex h-6 max-w-full shrink-0 items-center whitespace-nowrap px-3 text-litt-caption forced-colors:border \" + (hue ? \"rounded-litt-control \" + HUE[hue] : (raised ? RAISED : PLAIN) + ink) + \" \" + className}\n      {...rest}\n    >\n      {label}\n    </span>\n  );\n}\n",
      "target": "components/litt/bodies/tag.flat.tsx"
    }
  ],
  "docs": "Import Tag from components/litt/tag. Leave status out for a plain label, or set it to success, warning, danger or neutral: the text takes that color and there is no dot, so the word carries the status.",
  "meta": {
    "version": "2026.11.09",
    "released": "2026-11-09",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
