{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tag",
  "type": "registry:ui",
  "title": "Tag",
  "description": "A tiny inline anchor used to surface taxonomy (rule categories, plugin tags, article topics). Two exports because tag chips almost always appear in a wrapping cluster — `TagList` ships the canonical `<ul>`-of-`<li>` semantics so consumers don't reinvent…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "blog",
    "primitive"
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/skeleton.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/tag.tsx",
      "target": "components/ui/tag.tsx",
      "type": "registry:ui",
      "content": "import * as React from 'react';\n\n// @interlace/tag v1.3.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/tag\n// What changed since: https://ds.interlace.tools/c/tag#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Tag + TagList\n *\n * A tiny inline anchor used to surface taxonomy (rule categories, plugin\n * tags, article topics). Two exports because tag chips almost always\n * appear in a wrapping cluster — `TagList` ships the canonical\n * `<ul>`-of-`<li>` semantics so consumers don't reinvent the markup\n * (and don't accidentally drop the list role the way a bare flex of\n * anchors would).\n *\n * Server component — no hooks, just a styled `<a>`. Native focus,\n * native activation, native keyboard handling. The hover affordance\n * (brand-primary border) follows the brand-accent rule (only on interactive\n * surfaces; never on plain text).\n *\n * ## Anatomy\n *\n *   TagList                            (ul — data-min-viewport=320)\n *     └─ li (one per item)\n *          └─ Tag                      (anchor — data-min-viewport=320)\n *               └─ children\n *\n * ## MIN_VIEWPORT — 320\n *\n * Tag clusters wrap; nothing in this primitive assumes desktop width.\n * The `flex-wrap` in `TagList` is the explicit narrow-screen contract.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el + VariantProps | `React.ComponentProps<'a'> & VariantProps<...>`             |\n * | R6   | data-slot on the part            | `data-slot=\"tag\"` / `data-slot=\"tag-list\"`                  |\n * | R7   | className merged + ...rest       | `cn(tagVariants(...), className)` + `{...props}`            |\n * | R8   | No `isXxx`; enums for >2 states  | `tone` is an enum (default | primary | muted)               |\n * | R10  | Composition seam                 | `TagList` renders `Tag` per item; consumer can also use Tag directly |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R18  | Tailwind only                    | zero inline `style`; cva classes only                       |\n * | R19  | Tokens only                      | radius `--radius-full`, padding `--spacing-*`, type `--text-xs`, border `border-border` |\n * | R20  | AA contrast                      | tones use semantic foreground/muted tokens that clear AA    |\n * | R25  | Server component                 | no hooks → no `'use client'`                                |\n * | R26  | A11y from native el              | `<a href>` owns focus + activation; `<ul>` owns list semantics |\n */\n\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\nimport { Skeleton } from '@/components/ui/skeleton';\n\n/**\n * Minimum viable viewport (CSS px) for this primitive. Below it, the\n * preflight contract draws a dev-mode outline; in prod the component\n * still renders. Exported so consumers / tests can read it.\n */\nexport const MIN_VIEWPORT = 320 as const;\n\nconst tagVariants = cva(\n  [\n    // Pill anchor — rounded-full border, compact padding, small UI type.\n    //\n    // `bg-background` belongs to the base, not to a tone, because EVERY tone\n    // below names a foreground and none of them named a surface. Transparent,\n    // a tag took whatever it was dropped on: `tone=\"primary\"` inside a\n    // `<CTASection tone=\"primary\">` was `text-primary` on `bg-primary`, i.e.\n    // 1.00:1, and `default` was 2.23:1 light / 1.44:1 dark. Painting the\n    // surface here keeps the whole tone API intact — each tone is once again\n    // measured against the background it was designed for — and costs one\n    // class instead of a redesign. Locked by composite-contrast-lock.\n    'inline-flex items-center rounded-full border border-border bg-background',\n    'px-2.5 py-0.5 text-xs',\n    'transition-colors',\n    // Brand-accent hover lift (only on the interactive surface).\n    'hover:border-primary/60',\n    // Standard focus ring contract.\n    'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n  ].join(' '),\n  {\n    variants: {\n      /**\n       * Tone of the chip surface. `default` inherits page foreground;\n       * `primary` lifts to the brand foreground; `muted` drops to the\n       * muted-foreground token for low-emphasis taxonomy (e.g. dates).\n       */\n      tone: {\n        default: 'text-foreground',\n        primary: 'text-primary',\n        muted: 'text-muted-foreground',\n      },\n    },\n    defaultVariants: {\n      tone: 'default',\n    },\n  },\n);\n\ninterface TagProps\n  extends Omit<React.ComponentProps<'a'>, 'href'>,\n    VariantProps<typeof tagVariants> {\n  /**\n   * Destination — required in the idle state because Tag is always a\n   * link, never a button. Optional when `loading={true}` (the skeleton\n   * has no link to render).\n   */\n  href?: string;\n  /**\n   * When true, render a `<Skeleton variant=\"tag\" />` (h-5 w-12 rounded\n   * pill) in place of the chip. Shape-matched so tag clusters stay\n   * aligned during data load.\n   */\n  loading?: boolean;\n}\n\n/** A single tag chip. Server component (no hooks). */\nconst Tag = React.forwardRef<HTMLAnchorElement, TagProps>(\n  ({ className, tone, href, loading, children, ...props }, ref) => {\n    if (loading) {\n      return (\n        <Skeleton\n          variant=\"tag\"\n          data-slot=\"tag\"\n          data-min-viewport={String(MIN_VIEWPORT)}\n          className={className}\n        />\n      );\n    }\n    return (\n      <a\n        ref={ref}\n        href={href}\n        data-slot=\"tag\"\n        data-min-viewport={String(MIN_VIEWPORT)}\n        data-tone={tone ?? undefined}\n        className={cn(tagVariants({ tone }), className)}\n        {...props}\n      >\n        {children}\n      </a>\n    );\n  },\n);\nTag.displayName = 'Tag';\n\n/**\n * One item in a {@link TagList}. Carries the destination + label and an\n * optional per-item tone override.\n */\nexport interface TagListItem {\n  label: React.ReactNode;\n  href: string;\n  tone?: VariantProps<typeof tagVariants>['tone'];\n}\n\ninterface TagListProps extends React.ComponentProps<'ul'> {\n  /** The chips to render. Each item maps to one `<li>` → `Tag`. */\n  items: TagListItem[];\n}\n\n/**\n * A wrapping cluster of {@link Tag} chips. Renders as an unordered list\n * for assistive-tech list semantics; visually a `flex-wrap` row with\n * `gap-2` rhythm. Server component (no hooks).\n */\nconst TagList = React.forwardRef<HTMLUListElement, TagListProps>(\n  ({ className, items, ...props }, ref) => {\n    return (\n      <ul\n        ref={ref}\n        data-slot=\"tag-list\"\n        data-min-viewport={String(MIN_VIEWPORT)}\n        className={cn('flex flex-wrap gap-2', className)}\n        {...props}\n      >\n        {items.map((item) => (\n          <li key={item.href}>\n            <Tag href={item.href} tone={item.tone}>\n              {item.label}\n            </Tag>\n          </li>\n        ))}\n      </ul>\n    );\n  },\n);\nTagList.displayName = 'TagList';\n\nexport { Tag, TagList, tagVariants };\nexport type { TagProps, TagListProps };\n"
    }
  ],
  "meta": {
    "tier": "primitive",
    "client": false,
    "minViewport": 320,
    "loading": true,
    "version": "1.3.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/tag\n\nInstalled to `components/ui/tag.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/tag';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/tag\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
