{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badge",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": [
    "motion",
    "class-variance-authority",
    "@radix-ui/react-slot"
  ],
  "devDependencies": [],
  "files": [
    {
      "path": "badge.tsx",
      "content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n  type CSSProperties,\n  type ForwardedRef,\n  forwardRef,\n  type HTMLAttributes,\n  type KeyboardEventHandler,\n  type MouseEventHandler,\n  type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst badgeColors = {\n  gray: \"#a3a3a3\",\n  red: \"#ef4444\",\n  orange: \"#f97316\",\n  amber: \"#f59e0b\",\n  yellow: \"#eab308\",\n  lime: \"#84cc16\",\n  green: \"#22c55e\",\n  emerald: \"#10b981\",\n  teal: \"#14b8a6\",\n  cyan: \"#06b6d4\",\n  blue: \"#3b82f6\",\n  indigo: \"#6366f1\",\n  violet: \"#8b5cf6\",\n  purple: \"#a855f7\",\n  fuchsia: \"#d946ef\",\n  pink: \"#ec4899\",\n  rose: \"#f43f5e\",\n} as const;\n\nconst badgeSemanticColors = {\n  success: \"green\",\n  warning: \"amber\",\n  error: \"red\",\n  info: \"blue\",\n} as const;\n\ntype BadgeColor = keyof typeof badgeColors;\ntype BadgeSemanticColor = keyof typeof badgeSemanticColors;\ntype BadgeColorProp = BadgeColor | BadgeSemanticColor;\ntype BadgeSize = \"sm\" | \"md\" | \"lg\";\n\nconst badgePaletteSemanticAliases = Object.fromEntries(\n  Object.entries(badgeSemanticColors).map(([semantic, palette]) => [\n    palette,\n    semantic,\n  ])\n) as Partial<Record<BadgeColor, BadgeSemanticColor>>;\n\nfunction preferBadgeSemanticColor(color: BadgeColorProp): BadgeColorProp {\n  if (color in badgeSemanticColors) {\n    return color;\n  }\n\n  return badgePaletteSemanticAliases[color as BadgeColor] ?? color;\n}\n\nfunction getBadgePlaygroundColorOptions(paletteColors: BadgeColor[]) {\n  const paletteOptions = paletteColors\n    .filter((color) => !badgePaletteSemanticAliases[color])\n    .map((color) => ({\n      label: color.charAt(0).toUpperCase() + color.slice(1),\n      value: color as BadgeColorProp,\n    }));\n\n  const semanticOptions = (\n    Object.keys(badgeSemanticColors) as BadgeSemanticColor[]\n  ).map((color) => ({\n    label: color.charAt(0).toUpperCase() + color.slice(1),\n    value: color,\n  }));\n\n  return [...paletteOptions, ...semanticOptions];\n}\n\nconst badgeDotSizes = {\n  sm: 6,\n  md: 7,\n  lg: 8,\n} as const;\n\nconst badgeRowClassName = {\n  sm: \"gap-1 px-2\",\n  md: \"gap-1.5 px-2.5\",\n  lg: \"gap-1.5 px-3\",\n} as const;\n\nconst badgeDismissibleRowClassName = {\n  sm: \"gap-0.5 pl-2 pr-1\",\n  md: \"gap-0.5 pl-2.5 pr-1\",\n  lg: \"gap-0.5 pl-3 pr-1\",\n} as const;\n\nconst badgeDismissButtonSizes = {\n  sm: \"size-3.5 [&_svg]:size-2.5\",\n  md: \"size-3.5 [&_svg]:size-3\",\n  lg: \"size-4 [&_svg]:size-3\",\n} as const;\n\nconst badgeIconClassName = {\n  sm: \"[&_svg]:size-2.5\",\n  md: \"[&_svg]:size-3\",\n  lg: \"[&_svg]:size-3.5\",\n} as const;\n\ntype BadgeColorVars = CSSProperties & {\n  \"--badge-bg\"?: string;\n  \"--badge-fg\"?: string;\n  \"--badge-dot\"?: string;\n  \"--badge-border\"?: string;\n};\n\nfunction getBadgeColorVariables(color: BadgeColor): BadgeColorVars {\n  if (color === \"gray\") {\n    return {\n      \"--badge-bg\": \"var(--ic-accent)\",\n      \"--badge-fg\": \"var(--ic-foreground)\",\n      \"--badge-dot\": \"var(--ic-muted-foreground)\",\n      \"--badge-border\": \"var(--ic-border)\",\n    };\n  }\n\n  const hex = badgeColors[color];\n\n  return {\n    \"--badge-bg\": `color-mix(in srgb, ${hex} 18%, var(--ic-background))`,\n    \"--badge-fg\": `color-mix(in srgb, ${hex} 72%, var(--ic-foreground))`,\n    \"--badge-dot\": hex,\n    \"--badge-border\": `color-mix(in srgb, ${hex} 22%, var(--ic-border))`,\n  };\n}\n\nconst badgeVariants = cva(\n  \"relative inline-flex items-center whitespace-nowrap rounded-full px-0 font-medium leading-none transition-colors\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-[var(--badge-bg)] text-[var(--badge-fg)]\",\n        dot: \"border border-[var(--badge-border,var(--ic-border))] bg-background text-[var(--badge-fg,var(--ic-foreground))]\",\n      },\n      size: {\n        sm: \"h-5 text-[11px]\",\n        md: \"h-6 text-[12px]\",\n        lg: \"h-7 text-[13px]\",\n      },\n      interactive: {\n        true: \"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        false: \"\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"md\",\n      interactive: false,\n    },\n  }\n);\n\ntype SpanHTMLAttributesForMotion = Omit<\n  HTMLAttributes<HTMLSpanElement>,\n  | \"onAnimationEnd\"\n  | \"onAnimationIteration\"\n  | \"onAnimationStart\"\n  | \"onDrag\"\n  | \"onDragEnd\"\n  | \"onDragEnter\"\n  | \"onDragExit\"\n  | \"onDragLeave\"\n  | \"onDragOver\"\n  | \"onDragStart\"\n  | \"onDrop\"\n  | \"color\"\n>;\n\ninterface BadgeProps\n  extends Omit<SpanHTMLAttributesForMotion, \"color\">,\n    Omit<VariantProps<typeof badgeVariants>, \"interactive\"> {\n  animate?: boolean;\n  asChild?: boolean;\n  color?: BadgeColorProp;\n  dismissLabel?: string;\n  icon?: ReactNode;\n  onDismiss?: MouseEventHandler<HTMLButtonElement>;\n  shimmer?: boolean;\n  waveColor?: string;\n}\n\nfunction resolveBadgeColor(color: BadgeColorProp): BadgeColor {\n  if (color in badgeSemanticColors) {\n    return badgeSemanticColors[color as BadgeSemanticColor];\n  }\n\n  if (color in badgeColors) {\n    return color as BadgeColor;\n  }\n\n  return \"gray\";\n}\n\nfunction BadgeDismissButton({\n  dismissLabel,\n  onDismiss,\n  size,\n}: {\n  dismissLabel?: string;\n  onDismiss: MouseEventHandler<HTMLButtonElement>;\n  size: BadgeSize;\n}) {\n  return (\n    <button\n      aria-label={dismissLabel ?? \"Remove\"}\n      className={cn(\n        \"relative z-20 inline-flex shrink-0 items-center justify-center rounded-full text-current/70 transition-colors hover:bg-[color-mix(in_srgb,currentColor_12%,transparent)] hover:text-current focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n        badgeDismissButtonSizes[size]\n      )}\n      onClick={(event) => {\n        event.stopPropagation();\n        onDismiss(event);\n      }}\n      type=\"button\"\n    >\n      <svg aria-hidden fill=\"none\" viewBox=\"0 0 12 12\">\n        <path\n          d=\"M3 3l6 6M9 3 3 9\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeWidth=\"1.75\"\n        />\n      </svg>\n    </button>\n  );\n}\n\nfunction BadgeStatusDot({\n  animate,\n  size,\n}: {\n  animate: boolean;\n  size: BadgeSize;\n}) {\n  const dotSize = badgeDotSizes[size];\n  const Dot = animate ? motion.span : \"span\";\n\n  return (\n    <Dot\n      {...(animate\n        ? {\n            animate: {\n              opacity: [0.5, 1, 0.5],\n              scale: [0.9, 1, 0.9],\n            },\n            transition: {\n              duration: 1.8,\n              repeat: Number.POSITIVE_INFINITY,\n              ease: \"easeInOut\",\n            },\n          }\n        : {})}\n      aria-hidden\n      className=\"shrink-0 rounded-full\"\n      style={{\n        width: dotSize,\n        height: dotSize,\n        backgroundColor: \"var(--badge-dot)\",\n      }}\n    />\n  );\n}\n\ntype BadgeBehavior = {\n  hasIcon: boolean;\n  isClickable: boolean;\n  isDefault: boolean;\n  mergedStyle: CSSProperties;\n  resolvedSize: BadgeSize;\n  rootClassName: string;\n  rowClassName: string;\n  shouldBlinkDot: boolean;\n  shouldEntrance: boolean;\n  shouldShimmer: boolean;\n  showDismiss: boolean;\n  useMotionRoot: boolean;\n};\n\nfunction getBadgeBehavior({\n  animate,\n  asChild,\n  className,\n  color,\n  icon,\n  onClick,\n  onDismiss,\n  reduceMotion,\n  shimmer,\n  size,\n  style,\n  variant,\n}: {\n  animate: boolean;\n  asChild: boolean;\n  className?: string;\n  color: BadgeColorProp;\n  icon?: ReactNode;\n  onClick?: HTMLAttributes<HTMLSpanElement>[\"onClick\"];\n  onDismiss?: MouseEventHandler<HTMLButtonElement>;\n  reduceMotion: boolean | null;\n  shimmer: boolean;\n  size: BadgeProps[\"size\"];\n  style?: CSSProperties;\n  variant: BadgeProps[\"variant\"];\n}): BadgeBehavior {\n  const resolvedSize = size ?? \"md\";\n  const isDefault = variant === \"default\";\n  const hasIcon = icon != null;\n  const showDismiss = Boolean(onDismiss) && isDefault && !asChild && !hasIcon;\n  const isClickable = Boolean(onClick);\n  const shouldEntrance = animate && !reduceMotion && isDefault && !asChild;\n  const shouldShimmer = shimmer && !reduceMotion && isDefault && !asChild;\n  const shouldBlinkDot = animate && !isDefault && !reduceMotion && !asChild;\n  const colorVariables = getBadgeColorVariables(resolveBadgeColor(color));\n\n  return {\n    resolvedSize,\n    isDefault,\n    hasIcon,\n    showDismiss,\n    isClickable,\n    shouldEntrance,\n    shouldShimmer,\n    shouldBlinkDot,\n    useMotionRoot: shouldEntrance,\n    mergedStyle: { ...colorVariables, ...style },\n    rootClassName: cn(\n      componentThemeClassName,\n      badgeVariants({\n        variant,\n        size: resolvedSize,\n        interactive: isClickable,\n      }),\n      shouldShimmer && \"overflow-hidden\",\n      className\n    ),\n    rowClassName: showDismiss\n      ? badgeDismissibleRowClassName[resolvedSize]\n      : badgeRowClassName[resolvedSize],\n  };\n}\n\nfunction createBadgeRootKeyDownHandler({\n  isClickable,\n  onClick,\n  onKeyDown,\n}: {\n  isClickable: boolean;\n  onClick?: HTMLAttributes<HTMLSpanElement>[\"onClick\"];\n  onKeyDown?: KeyboardEventHandler<HTMLSpanElement>;\n}): KeyboardEventHandler<HTMLSpanElement> {\n  return (event) => {\n    onKeyDown?.(event);\n\n    if (event.defaultPrevented || !isClickable) {\n      return;\n    }\n\n    if (event.key === \"Enter\" || event.key === \" \") {\n      event.preventDefault();\n      onClick?.(event as unknown as React.MouseEvent<HTMLSpanElement>);\n    }\n  };\n}\n\nfunction buildBadgeRootProps({\n  behavior,\n  onClick,\n  onKeyDown,\n  props,\n  ref,\n}: {\n  behavior: BadgeBehavior;\n  onClick?: HTMLAttributes<HTMLSpanElement>[\"onClick\"];\n  onKeyDown?: KeyboardEventHandler<HTMLSpanElement>;\n  props: SpanHTMLAttributesForMotion;\n  ref: ForwardedRef<HTMLSpanElement>;\n}) {\n  const handleRootKeyDown = createBadgeRootKeyDownHandler({\n    isClickable: behavior.isClickable,\n    onClick,\n    onKeyDown,\n  });\n\n  return {\n    ...props,\n    className: behavior.rootClassName,\n    \"data-slot\": \"badge\",\n    ref,\n    style: behavior.mergedStyle,\n    ...(behavior.useMotionRoot\n      ? {\n          animate: { opacity: 1, scale: 1 },\n          initial: { opacity: 0, scale: 0.95 },\n          transition: { duration: 0.3 },\n        }\n      : {}),\n    ...(behavior.isClickable\n      ? {\n          role: props.role ?? \"button\",\n          tabIndex: props.tabIndex ?? 0,\n        }\n      : {}),\n    onClick: behavior.isClickable ? onClick : undefined,\n    onKeyDown:\n      behavior.isClickable || onKeyDown ? handleRootKeyDown : onKeyDown,\n  };\n}\n\nfunction BadgeShimmer({ waveColor }: { waveColor?: string }) {\n  return (\n    <motion.span\n      animate={{ x: [\"-100%\", \"200%\"] }}\n      aria-hidden\n      className=\"pointer-events-none absolute inset-0 rounded-full\"\n      style={{\n        background: `linear-gradient(90deg, transparent 0%, ${\n          waveColor ?? \"color-mix(in srgb, currentColor 18%, transparent)\"\n        } 50%, transparent 100%)`,\n      }}\n      transition={{\n        duration: 2,\n        repeat: Number.POSITIVE_INFINITY,\n        repeatDelay: 1.5,\n        ease: \"easeInOut\",\n      }}\n    />\n  );\n}\n\nfunction BadgeContentRow({\n  behavior,\n  children,\n  dismissLabel,\n  icon,\n  onDismiss,\n}: {\n  behavior: BadgeBehavior;\n  children: ReactNode;\n  dismissLabel?: string;\n  icon?: ReactNode;\n  onDismiss?: MouseEventHandler<HTMLButtonElement>;\n}) {\n  return (\n    <span\n      className={cn(\n        \"relative z-10 inline-flex h-full min-w-0 items-center\",\n        behavior.rowClassName\n      )}\n    >\n      {behavior.isDefault ? null : (\n        <BadgeStatusDot\n          animate={behavior.shouldBlinkDot}\n          size={behavior.resolvedSize}\n        />\n      )}\n      {behavior.isDefault && behavior.hasIcon ? (\n        <span\n          aria-hidden\n          className={cn(\n            \"inline-flex shrink-0\",\n            badgeIconClassName[behavior.resolvedSize]\n          )}\n        >\n          {icon}\n        </span>\n      ) : null}\n      <span className={cn(\"min-w-0\", behavior.showDismiss && \"truncate\")}>\n        {children}\n      </span>\n      {behavior.showDismiss && onDismiss ? (\n        <BadgeDismissButton\n          dismissLabel={dismissLabel}\n          onDismiss={onDismiss}\n          size={behavior.resolvedSize}\n        />\n      ) : null}\n    </span>\n  );\n}\n\nconst Badge = forwardRef<HTMLSpanElement, BadgeProps>(\n  (\n    {\n      animate = true,\n      asChild = false,\n      className,\n      color = \"gray\",\n      dismissLabel,\n      icon,\n      onClick,\n      onDismiss,\n      onKeyDown,\n      shimmer = true,\n      size = \"md\",\n      style,\n      variant = \"default\",\n      waveColor,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const reduceMotion = useReducedMotion();\n    const behavior = getBadgeBehavior({\n      animate,\n      asChild,\n      className,\n      color,\n      icon,\n      onClick,\n      onDismiss,\n      reduceMotion,\n      shimmer,\n      size,\n      style,\n      variant,\n    });\n\n    if (asChild) {\n      return (\n        <Slot\n          className={behavior.rootClassName}\n          data-slot=\"badge\"\n          onClick={onClick}\n          ref={ref}\n          style={behavior.mergedStyle}\n          {...props}\n        >\n          {children}\n        </Slot>\n      );\n    }\n\n    const Root = behavior.useMotionRoot ? motion.span : \"span\";\n    const rootProps = buildBadgeRootProps({\n      behavior,\n      onClick,\n      onKeyDown,\n      props,\n      ref,\n    });\n\n    return (\n      <Root {...rootProps}>\n        {behavior.shouldShimmer ? <BadgeShimmer waveColor={waveColor} /> : null}\n        <BadgeContentRow\n          behavior={behavior}\n          dismissLabel={dismissLabel}\n          icon={icon}\n          onDismiss={onDismiss}\n        >\n          {children}\n        </BadgeContentRow>\n      </Root>\n    );\n  }\n);\n\nBadge.displayName = \"Badge\";\n\nexport {\n  Badge,\n  badgeColors,\n  badgeSemanticColors,\n  badgeVariants,\n  getBadgeColorVariables,\n  getBadgePlaygroundColorOptions,\n  preferBadgeSemanticColor,\n  resolveBadgeColor,\n};\nexport type { BadgeColor, BadgeColorProp, BadgeProps, BadgeSemanticColor };\nexport default Badge;\n",
      "type": "registry:ui"
    }
  ],
  "title": "Badge",
  "description": "Preset-color badge with tinted fills, semantic aliases, animated default shimmer, dot variant, dismiss controls, and reduced-motion aware motion. Motion + CVA + Slot."
}
