{
  "name": "badges",
  "layer": "base",
  "type": "component",
  "title": "Badge components",
  "description": "Free and open-source React badge components built for modern applications and websites. These badges are built using React Aria and styled with Tailwind CSS.",
  "files": [
    {
      "path": "components/base/badges/badge-groups.tsx",
      "target": "components/base/badges/badge-groups.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport type { FC, ReactNode } from \"react\";\nimport { isValidElement } from \"react\";\nimport { ArrowRight } from \"@properui/icons\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { isReactComponent } from \"@/utils/is-react-component\";\n\ntype Size = \"md\" | \"lg\";\ntype Color = \"brand\" | \"warning\" | \"error\" | \"gray\" | \"success\";\ntype Theme = \"light\" | \"modern\";\ntype Align = \"leading\" | \"trailing\";\n\nconst baseClasses: Record<Theme, { root?: string; addon?: string; icon?: string }> = {\n    light: {\n        root: \"rounded-full ring-1 ring-inset\",\n        addon: \"rounded-full ring-1 ring-inset\",\n    },\n    modern: {\n        root: \"rounded-[10px] bg-primary text-secondary shadow-xs ring-1 ring-inset ring-primary hover:bg-secondary\",\n        addon: \"flex items-center rounded-md bg-primary shadow-xs ring-1 ring-inset ring-primary\",\n        icon: \"text-utility-neutral-500\",\n    },\n};\n\nconst getSizeClasses = (\n    theme?: Theme,\n    text?: boolean,\n    icon?: boolean,\n): Record<Align, Record<Size, { root?: string; addon?: string; icon?: string; dot?: string }>> => ({\n    leading: {\n        md: {\n            root: cx(\"py-1 pe-2 ps-1 text-xs font-medium\", !text && !icon && \"pe-1\"),\n            addon: cx(\"px-2 py-0.5\", theme === \"modern\" && \"gap-1 px-1.5\", text && \"me-2\"),\n            icon: \"ms-1 size-4\",\n        },\n        lg: {\n            root: cx(\"py-1 pe-2 ps-1 text-sm font-medium\", !text && !icon && \"pe-1\"),\n            addon: cx(\"px-2.5 py-0.5\", theme === \"modern\" && \"gap-1.5 px-2\", text && \"me-2\"),\n            icon: \"ms-1 size-4\",\n        },\n    },\n    trailing: {\n        md: {\n            root: cx(\"py-1 pe-1 ps-3 text-xs font-medium\", theme === \"modern\" && \"ps-2.5\"),\n            addon: cx(\"py-0.5 pe-1.5 ps-2\", theme === \"modern\" && \"pe-1.5 ps-2\", text && \"ms-2\"),\n            icon: \"ms-0.5 size-3 stroke-[3px]\",\n            dot: \"me-1.5\",\n        },\n        lg: {\n            root: \"py-1 pe-1 ps-3 text-sm font-medium\",\n            addon: cx(\"py-0.5 pe-2 ps-2.5\", theme === \"modern\" && \"pe-1.5 ps-2\", text && \"ms-2\"),\n            icon: \"ms-1 size-3 stroke-[3px]\",\n            dot: \"me-2\",\n        },\n    },\n});\n\nconst colorClasses: Record<Theme, Record<Color, { root?: string; addon?: string; icon?: string; dot?: string }>> = sortCx({\n    light: {\n        brand: {\n            root: \"bg-utility-brand-50 text-utility-brand-700 ring-utility-brand-200 hover:bg-utility-brand-100\",\n            addon: \"bg-primary text-current ring-utility-brand-200\",\n            icon: \"text-utility-brand-500\",\n        },\n        gray: {\n            root: \"bg-utility-neutral-50 text-utility-neutral-700 ring-utility-neutral-200 hover:bg-utility-neutral-100\",\n            addon: \"bg-primary text-current ring-utility-neutral-200\",\n            icon: \"text-utility-neutral-500\",\n        },\n        error: {\n            root: \"bg-utility-red-50 text-utility-red-700 ring-utility-red-200 hover:bg-utility-red-100\",\n            addon: \"bg-primary text-current ring-utility-red-200\",\n            icon: \"text-utility-red-500\",\n        },\n        warning: {\n            root: \"bg-utility-yellow-50 text-utility-yellow-700 ring-utility-yellow-200 hover:bg-utility-yellow-100\",\n            addon: \"bg-primary text-current ring-utility-yellow-200\",\n            icon: \"text-utility-yellow-500\",\n        },\n        success: {\n            root: \"bg-utility-green-50 text-utility-green-700 ring-utility-green-200 hover:bg-utility-green-100\",\n            addon: \"bg-primary text-current ring-utility-green-200\",\n            icon: \"text-utility-green-500\",\n        },\n    },\n    modern: {\n        brand: {\n            dot: \"bg-utility-brand-500 outline-3 -outline-offset-1 outline-utility-brand-100\",\n        },\n        gray: {\n            dot: \"bg-utility-neutral-500 outline-3 -outline-offset-1 outline-utility-neutral-100\",\n        },\n        error: {\n            dot: \"bg-utility-red-500 outline-3 -outline-offset-1 outline-utility-red-100\",\n        },\n        warning: {\n            dot: \"bg-utility-yellow-500 outline-3 -outline-offset-1 outline-utility-yellow-100\",\n        },\n        success: {\n            dot: \"bg-utility-green-500 outline-3 -outline-offset-1 outline-utility-green-100\",\n        },\n    },\n});\n\ninterface BadgeGroupProps {\n    /** Content rendered next to the addon. */\n    children?: string | ReactNode;\n    /** The label rendered inside the addon chip. */\n    addonText: string;\n    /** Size of the badge group. */\n    size?: Size;\n    /** Color of the badge group. */\n    color: Color;\n    /** Visual style of the badge group. */\n    theme?: Theme;\n    /**\n     * Alignment of the badge addon element.\n     */\n    align?: Align;\n    /** Icon (or element) rendered at the trailing edge. */\n    iconTrailing?: FC<{ className?: string }> | ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const BadgeGroup = ({\n    children,\n    addonText,\n    size = \"md\",\n    color = \"brand\",\n    theme = \"light\",\n    align = \"leading\",\n    className,\n    iconTrailing: IconTrailing = ArrowRight,\n}: BadgeGroupProps) => {\n    const colors = colorClasses[theme][color];\n    const sizes = getSizeClasses(theme, !!children, !!IconTrailing)[align][size];\n\n    const rootClasses = cx(\n        \"inline-flex w-max cursor-pointer items-center transition duration-100 ease-linear\",\n        baseClasses[theme].root,\n        sizes.root,\n        colors.root,\n        className,\n    );\n    const addonClasses = cx(\"inline-flex items-center\", baseClasses[theme].addon, sizes.addon, colors.addon);\n    const dotClasses = cx(\"inline-block size-2 shrink-0 rounded-full\", sizes.dot, colors.dot);\n    const iconClasses = cx(baseClasses[theme].icon, sizes.icon, colors.icon);\n\n    if (align === \"trailing\") {\n        return (\n            <div className={rootClasses}>\n                {theme === \"modern\" && <span className={dotClasses} />}\n\n                {children}\n\n                <span className={addonClasses}>\n                    {addonText}\n\n                    {/* Trailing icon */}\n                    {isReactComponent(IconTrailing) && <IconTrailing className={iconClasses} />}\n                    {isValidElement(IconTrailing) && IconTrailing}\n                </span>\n            </div>\n        );\n    }\n\n    return (\n        <div className={rootClasses}>\n            <span className={addonClasses}>\n                {theme === \"modern\" && <span className={dotClasses} />}\n                {addonText}\n            </span>\n\n            {children}\n\n            {/* Trailing icon */}\n            {isReactComponent(IconTrailing) && <IconTrailing className={iconClasses} />}\n            {isValidElement(IconTrailing) && IconTrailing}\n        </div>\n    );\n};\n",
      "dependencies": [
        "@properui/icons",
        "react"
      ]
    },
    {
      "path": "components/base/badges/badge-types.ts",
      "target": "components/base/badges/badge-types.ts",
      "type": "component",
      "content": "export type IconComponentType = React.FunctionComponent<{ className?: string; strokeWidth?: string | number }>;\n\nexport type Sizes = \"sm\" | \"md\" | \"lg\";\n\nexport type BadgeColors = \"gray\" | \"brand\" | \"error\" | \"warning\" | \"success\" | \"slate\" | \"sky\" | \"blue\" | \"indigo\" | \"purple\" | \"pink\" | \"orange\";\n\nexport type FlagTypes =\n    | \"AD\"\n    | \"AE\"\n    | \"AF\"\n    | \"AG\"\n    | \"AI\"\n    | \"AL\"\n    | \"AM\"\n    | \"AO\"\n    | \"AR\"\n    | \"AS\"\n    | \"AT\"\n    | \"AU\"\n    | \"AW\"\n    | \"AX\"\n    | \"AZ\"\n    | \"BA\"\n    | \"BB\"\n    | \"BD\"\n    | \"BE\"\n    | \"BF\"\n    | \"BG\"\n    | \"BH\"\n    | \"BI\"\n    | \"BJ\"\n    | \"BL\"\n    | \"BM\"\n    | \"BN\"\n    | \"BO\"\n    | \"BQ-1\"\n    | \"BQ-2\"\n    | \"BQ\"\n    | \"BR\"\n    | \"BS\"\n    | \"BT\"\n    | \"BW\"\n    | \"BY\"\n    | \"BZ\"\n    | \"CA\"\n    | \"CC\"\n    | \"CD-1\"\n    | \"CD\"\n    | \"CF\"\n    | \"CH\"\n    | \"CK\"\n    | \"CL\"\n    | \"CM\"\n    | \"CN\"\n    | \"CO\"\n    | \"CR\"\n    | \"CU\"\n    | \"CW\"\n    | \"CX\"\n    | \"CY\"\n    | \"CZ\"\n    | \"DE\"\n    | \"DJ\"\n    | \"DK\"\n    | \"DM\"\n    | \"DO\"\n    | \"DS\"\n    | \"DZ\"\n    | \"earth\"\n    | \"EC\"\n    | \"EE\"\n    | \"EG\"\n    | \"EH\"\n    | \"ER\"\n    | \"ES\"\n    | \"ET\"\n    | \"FI\"\n    | \"FJ\"\n    | \"FK\"\n    | \"FM\"\n    | \"FO\"\n    | \"FR\"\n    | \"GA\"\n    | \"GB-2\"\n    | \"GB\"\n    | \"GD\"\n    | \"GE\"\n    | \"GG\"\n    | \"GH\"\n    | \"GI\"\n    | \"GL\"\n    | \"GM\"\n    | \"GN\"\n    | \"GQ\"\n    | \"GR\"\n    | \"GT\"\n    | \"GU\"\n    | \"GW\"\n    | \"GY\"\n    | \"HK\"\n    | \"HN\"\n    | \"HR\"\n    | \"HT\"\n    | \"HU\"\n    | \"ID\"\n    | \"IE\"\n    | \"IL\"\n    | \"IM\"\n    | \"IN\"\n    | \"IO\"\n    | \"IQ\"\n    | \"IR\"\n    | \"IS\"\n    | \"IT\"\n    | \"JE\"\n    | \"JM\"\n    | \"JO\"\n    | \"JP\"\n    | \"KE\"\n    | \"KG\"\n    | \"KH\"\n    | \"KI\"\n    | \"KM\"\n    | \"KN\"\n    | \"KP\"\n    | \"KR\"\n    | \"KW\"\n    | \"KY\"\n    | \"KZ\"\n    | \"LA\"\n    | \"LB\"\n    | \"LC\"\n    | \"LI\"\n    | \"LK\"\n    | \"LR\"\n    | \"LS\"\n    | \"LT\"\n    | \"LU\"\n    | \"LV\"\n    | \"LY\"\n    | \"MA\"\n    | \"MC\"\n    | \"MD\"\n    | \"ME\"\n    | \"MG\"\n    | \"MH\"\n    | \"MK\"\n    | \"ML\"\n    | \"MM\"\n    | \"MN\"\n    | \"MO\"\n    | \"MP\"\n    | \"MQ\"\n    | \"MR\"\n    | \"MS\"\n    | \"MT\"\n    | \"MU\"\n    | \"MV\"\n    | \"MW\"\n    | \"MX\"\n    | \"MY\"\n    | \"MZ\"\n    | \"NA\"\n    | \"NE\"\n    | \"NF\"\n    | \"NG\"\n    | \"NI\"\n    | \"NL\"\n    | \"NO\"\n    | \"NP\"\n    | \"NR\"\n    | \"NU\"\n    | \"NZ\"\n    | \"OM\"\n    | \"PA\"\n    | \"PE\"\n    | \"PF\"\n    | \"PG\"\n    | \"PH\"\n    | \"PK\"\n    | \"PL\"\n    | \"PM\"\n    | \"PN\"\n    | \"PR\"\n    | \"PT\"\n    | \"PW\"\n    | \"PY\"\n    | \"QA\"\n    | \"RE\"\n    | \"RO\"\n    | \"RS\"\n    | \"RU\"\n    | \"RW\"\n    | \"SA\"\n    | \"SB\"\n    | \"SC\"\n    | \"SD\"\n    | \"SE\"\n    | \"SG\"\n    | \"SH\"\n    | \"SI\"\n    | \"SJ\"\n    | \"SK\"\n    | \"SL\"\n    | \"SM\"\n    | \"SN\"\n    | \"SO\"\n    | \"SR\"\n    | \"SS\"\n    | \"ST\"\n    | \"SV\"\n    | \"SX\"\n    | \"SY\"\n    | \"SZ\"\n    | \"TC\"\n    | \"TD\"\n    | \"TF\"\n    | \"TG\"\n    | \"TH\"\n    | \"TJ\"\n    | \"TK\"\n    | \"TL\"\n    | \"TM\"\n    | \"TN\"\n    | \"TO\"\n    | \"TR\"\n    | \"TT\"\n    | \"TV\"\n    | \"TZ\"\n    | \"UA\"\n    | \"UG\"\n    | \"UM\"\n    | \"US\"\n    | \"UY\"\n    | \"UZ\"\n    | \"VA\"\n    | \"VC\"\n    | \"VE\"\n    | \"VG\"\n    | \"VI\"\n    | \"VN\"\n    | \"VU\"\n    | \"WF\"\n    | \"WS\"\n    | \"YE\"\n    | \"YT\"\n    | \"ZA\"\n    | \"ZM\"\n    | \"ZW\";\n\nexport type ExtractColorKeys<T> = T extends { styles: infer C } ? keyof C : never;\nexport type ExtractBadgeKeys<T> = keyof T;\nexport type BadgeTypeToColorMap<T> = {\n    [K in ExtractBadgeKeys<T>]: ExtractColorKeys<T[K]>;\n};\nexport type BadgeTypeColors<T> = ExtractColorKeys<T[keyof T]>;\n\nexport const badgeTypes = {\n    pillColor: \"pill-color\",\n    badgeColor: \"color\",\n    badgeModern: \"modern\",\n} as const;\n\nexport type BadgeTypes = (typeof badgeTypes)[keyof typeof badgeTypes];\n",
      "dependencies": []
    },
    {
      "path": "components/base/badges/badges.tsx",
      "target": "components/base/badges/badges.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport type { HTMLAttributes, MouseEventHandler, ReactNode } from \"react\";\nimport { X as CloseX } from \"@properui/icons\";\nimport { cx } from \"@/utils/cx\";\nimport type { BadgeColors, BadgeTypeToColorMap, BadgeTypes, FlagTypes, IconComponentType, Sizes } from \"./badge-types\";\nimport { badgeTypes } from \"./badge-types\";\n\n/**\n * TODO(orchestrator): candidate for `components/foundations/dot-icon.tsx` (see\n * `the project documentation`). Duplicated locally\n * because this task's `owns` glob is limited to `components/base/badges/**`.\n */\nconst Dot = ({ size = \"md\", ...props }: HTMLAttributes<HTMLOrSVGElement> & { size?: \"sm\" | \"md\" }) => {\n    const sizes = {\n        sm: { wh: 8, c: 4, r: 2.5 },\n        md: { wh: 10, c: 5, r: 4 },\n    };\n\n    return (\n        <svg width={sizes[size].wh} height={sizes[size].wh} viewBox={`0 0 ${sizes[size].wh} ${sizes[size].wh}`} fill=\"none\" {...props}>\n            <circle cx={sizes[size].c} cy={sizes[size].c} r={sizes[size].r} fill=\"currentColor\" stroke=\"currentColor\" />\n        </svg>\n    );\n};\n\nexport const filledColors: Record<BadgeColors, { root: string; addon: string; addonButton: string }> = {\n    gray: {\n        root: \"bg-utility-neutral-50 text-utility-neutral-700 ring-utility-neutral-200\",\n        addon: \"text-utility-neutral-500\",\n        addonButton: \"hover:bg-utility-neutral-100 text-utility-neutral-400 hover:text-utility-neutral-500\",\n    },\n    brand: {\n        root: \"bg-utility-brand-50 text-utility-brand-700 ring-utility-brand-200\",\n        addon: \"text-utility-brand-500\",\n        addonButton: \"hover:bg-utility-brand-100 text-utility-brand-400 hover:text-utility-brand-500\",\n    },\n    error: {\n        root: \"bg-utility-red-50 text-utility-red-700 ring-utility-red-200\",\n        addon: \"text-utility-red-500\",\n        addonButton: \"hover:bg-utility-red-100 text-utility-red-400 hover:text-utility-red-500\",\n    },\n    warning: {\n        root: \"bg-utility-yellow-50 text-utility-yellow-700 ring-utility-yellow-200\",\n        addon: \"text-utility-yellow-500\",\n        addonButton: \"hover:bg-utility-yellow-100 text-utility-yellow-400 hover:text-utility-yellow-500\",\n    },\n    success: {\n        root: \"bg-utility-green-50 text-utility-green-700 ring-utility-green-200\",\n        addon: \"text-utility-green-500\",\n        addonButton: \"hover:bg-utility-green-100 text-utility-green-400 hover:text-utility-green-500\",\n    },\n    slate: {\n        root: \"bg-utility-slate-50 text-utility-slate-700 ring-utility-slate-200\",\n        addon: \"text-utility-slate-500\",\n        addonButton: \"hover:bg-utility-slate-100 text-utility-slate-400 hover:text-utility-slate-500\",\n    },\n    sky: {\n        root: \"bg-utility-sky-50 text-utility-sky-700 ring-utility-sky-200\",\n        addon: \"text-utility-sky-500\",\n        addonButton: \"hover:bg-utility-sky-100 text-utility-sky-400 hover:text-utility-sky-500\",\n    },\n    blue: {\n        root: \"bg-utility-blue-50 text-utility-blue-700 ring-utility-blue-200\",\n        addon: \"text-utility-blue-500\",\n        addonButton: \"hover:bg-utility-blue-100 text-utility-blue-400 hover:text-utility-blue-500\",\n    },\n    indigo: {\n        root: \"bg-utility-indigo-50 text-utility-indigo-700 ring-utility-indigo-200\",\n        addon: \"text-utility-indigo-500\",\n        addonButton: \"hover:bg-utility-indigo-100 text-utility-indigo-400 hover:text-utility-indigo-500\",\n    },\n    purple: {\n        root: \"bg-utility-purple-50 text-utility-purple-700 ring-utility-purple-200\",\n        addon: \"text-utility-purple-500\",\n        addonButton: \"hover:bg-utility-purple-100 text-utility-purple-400 hover:text-utility-purple-500\",\n    },\n    pink: {\n        root: \"bg-utility-pink-50 text-utility-pink-700 ring-utility-pink-200\",\n        addon: \"text-utility-pink-500\",\n        addonButton: \"hover:bg-utility-pink-100 text-utility-pink-400 hover:text-utility-pink-500\",\n    },\n    orange: {\n        root: \"bg-utility-orange-50 text-utility-orange-700 ring-utility-orange-200\",\n        addon: \"text-utility-orange-500\",\n        addonButton: \"hover:bg-utility-orange-100 text-utility-orange-400 hover:text-utility-orange-500\",\n    },\n};\n\nconst addonOnlyColors = Object.fromEntries(Object.entries(filledColors).map(([key, value]) => [key, { root: \"\", addon: value.addon }])) as Record<\n    BadgeColors,\n    { root: string; addon: string }\n>;\n\nconst withPillTypes = {\n    [badgeTypes.pillColor]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset\",\n        styles: filledColors,\n    },\n    [badgeTypes.badgeColor]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset\",\n        styles: filledColors,\n    },\n    [badgeTypes.badgeModern]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset shadow-xs\",\n        styles: {\n            gray: {\n                root: \"bg-primary text-secondary ring-primary\",\n                addon: \"text-neutral-500\",\n                addonButton: \"hover:bg-utility-neutral-100 text-utility-neutral-400 hover:text-utility-neutral-500\",\n            },\n        },\n    },\n};\n\nconst withBadgeTypes = {\n    [badgeTypes.pillColor]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset\",\n        styles: filledColors,\n    },\n    [badgeTypes.badgeColor]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset\",\n        styles: filledColors,\n    },\n    [badgeTypes.badgeModern]: {\n        common: \"size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset bg-primary text-secondary ring-primary shadow-xs\",\n        styles: addonOnlyColors,\n    },\n};\n\nexport type BadgeColor<T extends BadgeTypes> = BadgeTypeToColorMap<typeof withPillTypes>[T];\n\ninterface BadgeProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeColor<T>;\n    /** Badge label content. */\n    children: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const Badge = <T extends BadgeTypes>(props: BadgeProps<T>) => {\n    const { type = \"pill-color\", size = \"md\", color = \"gray\", children, className, ...rest } = props;\n    const colors = withPillTypes[type];\n\n    const pillSizes = {\n        sm: \"py-0.5 px-2 text-xs font-medium\",\n        md: \"py-0.5 px-2.5 text-sm font-medium\",\n        lg: \"py-1 px-3 text-sm font-medium\",\n    };\n    const badgeSizes = {\n        sm: \"py-0.5 px-1.5 text-xs font-medium\",\n        md: \"py-0.5 px-2 text-sm font-medium\",\n        lg: \"py-1 px-2.5 text-sm font-medium rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            {children}\n        </span>\n    );\n};\n\ninterface BadgeWithDotProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];\n    /** Additional classes merged onto the root element. */\n    className?: string;\n    /** Badge label content. */\n    children: ReactNode;\n}\n\nexport const BadgeWithDot = <T extends BadgeTypes>(props: BadgeWithDotProps<T>) => {\n    const { size = \"md\", color = \"gray\", type = \"pill-color\", className, children, ...rest } = props;\n\n    const colors = withBadgeTypes[type];\n\n    const pillSizes = {\n        sm: \"gap-1 py-0.5 ps-1.5 pe-2 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 ps-2 pe-2.5 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 ps-2.5 pe-3 text-sm font-medium\",\n    };\n\n    const badgeSizes = {\n        sm: \"gap-1 py-0.5 px-1.5 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 px-2 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 px-2.5 text-sm font-medium rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            <Dot className={colors.styles[color].addon} size=\"sm\" aria-hidden=\"true\" />\n            {children}\n        </span>\n    );\n};\n\ninterface BadgeWithIconProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];\n    /** Icon rendered before the label. */\n    iconLeading?: IconComponentType;\n    /** Icon rendered after the label. */\n    iconTrailing?: IconComponentType;\n    /** Badge label content. */\n    children: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const BadgeWithIcon = <T extends BadgeTypes>(props: BadgeWithIconProps<T>) => {\n    const { size = \"md\", color = \"gray\", type = \"pill-color\", iconLeading: IconLeading, iconTrailing: IconTrailing, children, className, ...rest } = props;\n\n    const colors = withBadgeTypes[type];\n\n    const icon = IconLeading ? \"leading\" : \"trailing\";\n\n    const pillSizes = {\n        sm: {\n            trailing: \"gap-0.5 py-0.5 ps-2 pe-1.5 text-xs font-medium\",\n            leading: \"gap-0.5 py-0.5 pe-2 ps-1.5 text-xs font-medium\",\n        },\n        md: {\n            trailing: \"gap-1 py-0.5 ps-2.5 pe-2 text-sm font-medium\",\n            leading: \"gap-1 py-0.5 pe-2.5 ps-2 text-sm font-medium\",\n        },\n        lg: {\n            trailing: \"gap-1 py-1 ps-3 pe-2.5 text-sm font-medium\",\n            leading: \"gap-1 py-1 pe-3 ps-2.5 text-sm font-medium\",\n        },\n    };\n    const badgeSizes = {\n        sm: {\n            trailing: \"gap-0.5 py-0.5 ps-2 pe-1.5 text-xs font-medium\",\n            leading: \"gap-0.5 py-0.5 pe-2 ps-1.5 text-xs font-medium\",\n        },\n        md: {\n            trailing: \"gap-1 py-0.5 ps-2 pe-1.5 text-sm font-medium\",\n            leading: \"gap-1 py-0.5 pe-2 ps-1.5 text-sm font-medium\",\n        },\n        lg: {\n            trailing: \"gap-1 py-1 ps-2.5 pe-2 text-sm font-medium rounded-lg\",\n            leading: \"gap-1 py-1 pe-2.5 ps-2 text-sm font-medium rounded-lg\",\n        },\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size][icon], colors.styles[color].root, className)}>\n            {IconLeading && <IconLeading aria-hidden=\"true\" className={cx(colors.styles[color].addon, \"size-3 stroke-3\")} />}\n            {children}\n            {IconTrailing && <IconTrailing aria-hidden=\"true\" className={cx(colors.styles[color].addon, \"size-3 stroke-3\")} />}\n        </span>\n    );\n};\n\ninterface BadgeWithFlagProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** ISO country code of the flag to render. */\n    flag?: FlagTypes;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withPillTypes>[T];\n    /** Badge label content. */\n    children: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const BadgeWithFlag = <T extends BadgeTypes>(props: BadgeWithFlagProps<T>) => {\n    const { size = \"md\", color = \"gray\", flag = \"AU\", type = \"pill-color\", children, className, ...rest } = props;\n\n    const colors = withPillTypes[type];\n\n    const pillSizes = {\n        sm: \"gap-1 py-0.5 ps-0.75 pe-2 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 ps-1 pe-2.5 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 ps-1.5 pe-3 text-sm font-medium\",\n    };\n    const badgeSizes = {\n        sm: \"gap-1 py-0.5 ps-1 pe-1.5 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 ps-1.5 pe-2 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 ps-2 pe-2.5 text-sm font-medium rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            <img src={`/flags/${flag}.svg`} className=\"size-4 max-w-none rounded-full\" alt={`${flag} flag`} />\n            {children}\n        </span>\n    );\n};\n\ninterface BadgeWithImageProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Image source rendered as a rounded avatar. */\n    imgSrc: string;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withPillTypes>[T];\n    /** Badge label content. */\n    children: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const BadgeWithImage = <T extends BadgeTypes>(props: BadgeWithImageProps<T>) => {\n    const { size = \"md\", color = \"gray\", type = \"pill-color\", imgSrc, children, className, ...rest } = props;\n\n    const colors = withPillTypes[type];\n\n    const pillSizes = {\n        sm: \"gap-1 py-0.5 ps-0.75 pe-2 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 ps-1 pe-2.5 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 ps-1.5 pe-3 text-sm font-medium\",\n    };\n    const badgeSizes = {\n        sm: \"gap-1 py-0.5 ps-1 pe-1.5 text-xs font-medium\",\n        md: \"gap-1.5 py-0.5 ps-1.5 pe-2 text-sm font-medium\",\n        lg: \"gap-1.5 py-1 ps-2 pe-2.5 text-sm font-medium rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            <img src={imgSrc} className=\"size-4 max-w-none rounded-full\" alt=\"\" />\n            {children}\n        </span>\n    );\n};\n\ninterface BadgeWithButtonProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Icon rendered inside the trailing button. Defaults to a close (X) icon. */\n    icon?: IconComponentType;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withPillTypes>[T];\n    /** Badge label content. */\n    children: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n    /**\n     * The label for the button.\n     */\n    buttonLabel?: string;\n    /**\n     * The click event handler for the button.\n     */\n    onButtonClick?: MouseEventHandler<HTMLButtonElement>;\n}\n\nexport const BadgeWithButton = <T extends BadgeTypes>(props: BadgeWithButtonProps<T>) => {\n    const { size = \"md\", color = \"gray\", type = \"pill-color\", icon: Icon = CloseX, buttonLabel, onButtonClick, children, className, ...rest } = props;\n\n    const colors = withPillTypes[type];\n\n    const pillSizes = {\n        sm: \"gap-0.5 py-0.5 ps-2 pe-0.75 text-xs font-medium\",\n        md: \"gap-0.5 py-0.5 ps-2.5 pe-1 text-sm font-medium\",\n        lg: \"gap-0.5 py-1 ps-3 pe-1.5 text-sm font-medium\",\n    };\n    const badgeSizes = {\n        sm: \"gap-0.5 py-0.5 ps-1.5 pe-0.75 text-xs font-medium\",\n        md: \"gap-0.5 py-0.5 ps-2 pe-1 text-sm font-medium\",\n        lg: \"gap-0.5 py-1 ps-2.5 pe-1.5 text-sm font-medium rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            {children}\n            <button\n                type=\"button\"\n                aria-label={buttonLabel}\n                onClick={onButtonClick}\n                className={cx(\n                    \"outline-focus-ring flex cursor-pointer items-center justify-center p-0.5 transition duration-100 ease-linear focus-visible:outline-2\",\n                    colors.styles[color].addonButton,\n                    type === \"pill-color\" ? \"rounded-full\" : \"rounded-[3px]\",\n                )}\n            >\n                <Icon aria-hidden=\"true\" className=\"transition-inherit-all size-3 stroke-[3px]\" />\n            </button>\n        </span>\n    );\n};\n\ninterface BadgeIconProps<T extends BadgeTypes> extends Omit<HTMLAttributes<HTMLSpanElement>, \"color\" | \"children\" | \"className\"> {\n    /** Visual style of the badge. */\n    type?: T;\n    /** Size of the badge. */\n    size?: Sizes;\n    /** Icon rendered inside the icon-only badge. */\n    icon: IconComponentType;\n    /** Color of the badge, scoped to the chosen `type`. */\n    color?: BadgeTypeToColorMap<typeof withPillTypes>[T];\n    /** Not used for icon-only badges; kept for API parity. */\n    children?: ReactNode;\n    /** Additional classes merged onto the root element. */\n    className?: string;\n}\n\nexport const BadgeIcon = <T extends BadgeTypes>(props: BadgeIconProps<T>) => {\n    const { size = \"md\", color = \"gray\", type = \"pill-color\", icon: Icon, className, ...rest } = props;\n\n    const colors = withPillTypes[type];\n\n    const pillSizes = {\n        sm: \"p-1.25\",\n        md: \"p-1.5\",\n        lg: \"p-2\",\n    };\n\n    const badgeSizes = {\n        sm: \"p-1.25\",\n        md: \"p-1.5\",\n        lg: \"p-2 rounded-lg\",\n    };\n\n    const sizes = {\n        [badgeTypes.pillColor]: pillSizes,\n        [badgeTypes.badgeColor]: badgeSizes,\n        [badgeTypes.badgeModern]: badgeSizes,\n    };\n\n    return (\n        <span {...rest} className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>\n            <Icon aria-hidden=\"true\" className={cx(\"size-3 stroke-[3px]\", colors.styles[color].addon)} />\n        </span>\n    );\n};\n",
      "dependencies": [
        "@properui/icons",
        "react"
      ]
    }
  ],
  "registryDependencies": [
    "cx",
    "is-react-component"
  ],
  "optionalRegistryDependencies": [],
  "dependencies": [
    "@properui/icons",
    "react"
  ],
  "cssVars": [],
  "examples": [
    "badge-example",
    "badge-group-example",
    "color",
    "color-with-icon-leading",
    "color-with-icon-only",
    "color-with-icon-trailing",
    "modern",
    "modern-leading-brand",
    "modern-leading-error",
    "modern-leading-gray",
    "modern-leading-success",
    "modern-leading-warning",
    "modern-trailing-brand",
    "modern-trailing-error",
    "modern-trailing-gray",
    "modern-trailing-success",
    "modern-trailing-warning",
    "modern-with-icon-leading",
    "modern-with-icon-only",
    "modern-with-icon-trailing",
    "pill-color",
    "pill-color-leading-brand",
    "pill-color-leading-error",
    "pill-color-leading-gray",
    "pill-color-leading-success",
    "pill-color-leading-warning",
    "pill-color-trailing-brand",
    "pill-color-trailing-error",
    "pill-color-trailing-gray",
    "pill-color-trailing-success",
    "pill-color-trailing-warning",
    "with-avatar",
    "with-avatar-badge-color",
    "with-avatar-badge-modern",
    "with-close-x",
    "with-close-x-badge-color",
    "with-close-x-badge-modern",
    "with-dot",
    "with-dot-badge-color",
    "with-dot-badge-modern",
    "with-flag",
    "with-flag-badge-color",
    "with-flag-badge-modern",
    "with-icon-leading",
    "with-icon-only",
    "with-icon-trailing"
  ],
  "docs": "/components/badges",
  "intent": "Label an item with a short status, category, or count using a color-coded tag.",
  "avoid_when": [
    "interactive filtering or removable multi-select input (use tags)",
    "the sole indicator of a critical state; color alone should never be the only signal"
  ],
  "composes_with": [
    "badge-groups",
    "table",
    "avatar",
    "tabs"
  ],
  "a11y_contract": [
    "decorative dot/leading/trailing icons are marked aria-hidden by the component",
    "the close/remove button on a removable badge takes its aria-label from the caller-supplied `buttonLabel`; set it or the control announces nothing useful"
  ],
  "requires_data": [
    "a short text label; the color prop conveys meaning only visually, not to assistive tech"
  ],
  "token_contract": [
    "bg-primary",
    "bg-secondary",
    "bg-utility-blue-100",
    "bg-utility-blue-50",
    "bg-utility-brand-100",
    "bg-utility-brand-50",
    "bg-utility-brand-500",
    "bg-utility-green-100",
    "bg-utility-green-50",
    "bg-utility-green-500",
    "bg-utility-indigo-100",
    "bg-utility-indigo-50",
    "bg-utility-neutral-100",
    "bg-utility-neutral-50",
    "bg-utility-neutral-500",
    "bg-utility-orange-100",
    "bg-utility-orange-50",
    "bg-utility-pink-100",
    "bg-utility-pink-50",
    "bg-utility-purple-100",
    "bg-utility-purple-50",
    "bg-utility-red-100",
    "bg-utility-red-50",
    "bg-utility-red-500",
    "bg-utility-sky-100",
    "bg-utility-sky-50",
    "bg-utility-slate-100",
    "bg-utility-slate-50",
    "bg-utility-yellow-100",
    "bg-utility-yellow-50",
    "bg-utility-yellow-500",
    "text-secondary",
    "text-utility-blue-400",
    "text-utility-blue-500",
    "text-utility-blue-700",
    "text-utility-brand-400",
    "text-utility-brand-500",
    "text-utility-brand-700",
    "text-utility-green-400",
    "text-utility-green-500",
    "text-utility-green-700",
    "text-utility-indigo-400",
    "text-utility-indigo-500",
    "text-utility-indigo-700",
    "text-utility-neutral-400",
    "text-utility-neutral-500",
    "text-utility-neutral-700",
    "text-utility-orange-400",
    "text-utility-orange-500",
    "text-utility-orange-700",
    "text-utility-pink-400",
    "text-utility-pink-500",
    "text-utility-pink-700",
    "text-utility-purple-400",
    "text-utility-purple-500",
    "text-utility-purple-700",
    "text-utility-red-400",
    "text-utility-red-500",
    "text-utility-red-700",
    "text-utility-sky-400",
    "text-utility-sky-500",
    "text-utility-sky-700",
    "text-utility-slate-400",
    "text-utility-slate-500",
    "text-utility-slate-700",
    "text-utility-yellow-400",
    "text-utility-yellow-500",
    "text-utility-yellow-700"
  ],
  "changelog": [
    {
      "version": "0.2.0",
      "changes": [
        "Field API and accessible-name fixes from the agent feedback map (docs/spec/feedback/2026-09-11-agent-feedback-map.md items 2.17, 2.20, 2.22, and the JSDoc half of 2.11), batched across `input`, `select`, `checkbox`, `radio-buttons`, `badges`, `toggle`, `tags`, `buttons/button`, `date-picker`, `page-headers`, and `foundations/featured-icon`:"
      ]
    }
  ],
  "platforms": [
    "react",
    "next"
  ]
}
