# Badge

Compact labels with semantic solid, light, and outline styles, five sizes, and Base UI composition.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





## Playground [#playground]

```tsx
"use client";

import { Badge } from "@/components/ui/badge";

export const BadgeDemo = () => (
  <Badge variant="default" size="default" radius="default">
    {"Ready"}
  </Badge>
);
```

## Installation [#installation]

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

```bash
npx shadcn@latest add https://vandor-ui.vercel.app/r/badge.json
```

## Dependencies [#dependencies]

Installed automatically by the registry CLI. For manual setup:

```bash
npm install @base-ui/react@^1.8.0 class-variance-authority@^0.7.1 cn@^0.4.0
```

## Usage [#usage]

```tsx
import { Badge } from "@/components/ui/badge";

export const Demo = () => <Badge variant="success-light">Ready</Badge>;
```

Use your project's configured UI alias if it differs from `@/components/ui`. Badge is a presentational label, not a data-fetching component. Applications own the status and its text.

## Examples [#examples]

### Variants [#variants]

Use solid styles for emphasis, light styles for quiet status indicators, and outline styles for metadata. Color never replaces a meaningful label.

<ComponentPreview name="badge-variants" title="badge-variants.tsx">
  <BadgeVariants />
</ComponentPreview>

The primary family uses Vandor's primary tokens. `focus`, `focus-light`, and `focus-outline` are ReUI-compatible aliases for the primary family, not keyboard-focus states. `invert` uses foreground/background tokens instead of an additional color scheme.

### Sizes and Pill Radius [#sizes-and-pill-radius]

The five sizes are `xs`, `sm`, `default`, `lg`, and `xl`. Prefer `default` or `xl` for status text. The smaller sizes are intended for dense metadata. `radius="default"` uses the theme's `rounded-md`; `radius="full"` makes a pill.

<ComponentPreview name="badge-sizes" title="badge-sizes.tsx">
  <BadgeSizes />
</ComponentPreview>

### Icons, Spinner, and Links [#icons-spinner-and-links]

Compose icons, dots, or a spinner as children. These are not Badge props. Icons inherit the current color, and SVG sizing is handled by Badge unless the icon supplies an explicit `size-*` class.

<ComponentPreview name="badge-composition" title="badge-composition.tsx">
  <BadgeComposition />
</ComponentPreview>

Use Base UI's `render` to replace the span with a link. Badge has no `asChild`, `disabled`, or `isLoading` prop. Give interactive elements their native semantics; use Button for actions rather than putting click behavior on a span. Badge merges public refs and handlers with the replacement element.

## Theme and Consumer Setup [#theme-and-consumer-setup]

Requires React 19, Tailwind CSS 4, and the standard shadcn theme tokens. CLI installation includes Base UI 1.8+, `class-variance-authority`, `cn`, and semantic CSS variables for both light and dark themes. It installs a single Badge source file with its MIT notice, not another component catalog.

Additional tokens are `--success`, `--info`, and `--warning`, each with `-foreground` for solid badges and `-text` for light/outline badges. `--destructive-foreground` and `--destructive-text` complete the destructive family. Tailwind's `@theme inline` mappings are included by the registry's `cssVars.theme`. The distinction avoids using white text on a pale status background. Review existing custom token values when installing into an already themed project.

If copying the Source manually, also copy the `cssVars.theme`, `cssVars.light`, and `cssVars.dark` entries from [the Badge registry artifact](/r/badge.json) into your Tailwind theme, `:root`, and `.dark` respectively. Copying the TSX alone does not supply the semantic colors. Preserve its license comment and install all listed dependencies. The icon/spinner examples additionally require `lucide-react`; Badge itself does not.

## Storybook Stories (Optional) [#storybook-stories-optional]

Install Badge first, then install the stories separately:

```bash
pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/badge-stories.json
```

The stories-only item does not reinstall Badge, modify `.storybook/`, or install/upgrade Storybook packages. Use `@storybook/react` matching your existing Storybook setup (verified against 10.6.1), plus `lucide-react` for the examples. Include `**/*.stories.tsx` in story discovery and import your Tailwind/theme CSS in preview setup. Light/dark decorators belong to the consumer. Stories include variants, sizes, pill, dot, icon, spinner, link, and long labels; no addons are required by the story source.

## Behavior & Accessibility [#behavior--accessibility]

* Badge renders a `span` by default and is not focusable. It does not announce every static label automatically.
* For changing async status, the application can supply `role="status"` and `aria-live="polite"`. Reserve a consistent toolbar slot when replacing status text to avoid layout jumps.
* Use “Live” only when your application actually updates data automatically. Successful fetches alone do not establish a live connection.
* Hide decorative icons/dots with `aria-hidden="true"`. Keep the status readable as text.
* `motion-safe:animate-spin` in the spinner example stops animation when reduced motion is preferred.
* Links retain native keyboard navigation and a visible focus ring. `variant="outline"` alone does not make a Badge a link.
* Labels stay on one line. For constrained layouts, keep labels short or provide a scrollable parent. Badge does not silently truncate meaningful status text.

## Props [#props]

| Prop      | Type                                                                                                                                                                                                                                                                                                                                                                                                  | Default   | Description                                                                                                                                                                      |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| children  | ReactNode                                                                                                                                                                                                                                                                                                                                                                                             | Not set   | Badge content. Keep visible text alongside status icons or dots.                                                                                                                 |
| className | string                                                                                                                                                                                                                                                                                                                                                                                                | Not set   | Classes merged with variant, size and radius defaults.                                                                                                                           |
| radius    | "default" \| "full"                                                                                                                                                                                                                                                                                                                                                                                   | "default" | Token-based rounded-md or a fully rounded pill.                                                                                                                                  |
| ref       | Ref\<HTMLSpanElement>                                                                                                                                                                                                                                                                                                                                                                                 | Not set   | React 19 ref to the rendered element, composed with a render element's ref.                                                                                                      |
| render    | ReactElement \| ComponentRenderFn                                                                                                                                                                                                                                                                                                                                                                     | Not set   | Base UI element or render function replacing the span. Preserves the replacement element's attributes, handlers and ref. No asChild prop.                                        |
| size      | "xs" \| "sm" \| "default" \| "lg" \| "xl"                                                                                                                                                                                                                                                                                                                                                             | "default" | Compact label dimensions. Prefer default or xl for status text; xs/sm are dense metadata sizes.                                                                                  |
| variant   | "default" \| "secondary" \| "outline" \| "success" \| "info" \| "warning" \| "destructive" \| "focus" \| "invert" \| "primary-light" \| "success-light" \| "info-light" \| "warning-light" \| "destructive-light" \| "focus-light" \| "invert-light" \| "primary-outline" \| "success-outline" \| "info-outline" \| "warning-outline" \| "destructive-outline" \| "focus-outline" \| "invert-outline" | "default" | Semantic solid, light or outline appearance. Focus uses Vandor's primary tokens; invert uses foreground/background. Appearance does not imply live data or interactive behavior. |

Badge forwards native span attributes, handlers and React 19 refs through Base UI useRender. Use render for links or other elements; Badge does not add button semantics, disabled behavior, or an automatic live region.

## Source [#source]

```tsx
"use client";

/**
 * Adapted from ReUI Badge (https://github.com/keenthemes/reui).
 * MIT License
 * Copyright (c) 2025 Keenthemes Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "cn";

const badgeVariants = cva(
  "relative inline-flex w-fit shrink-0 items-center justify-center gap-1 border border-transparent font-medium whitespace-nowrap outline-none transition-[color,box-shadow] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-3",
  {
    defaultVariants: { radius: "default", size: "default", variant: "default" },
    variants: {
      radius: { default: "rounded-md", full: "rounded-full" },
      size: {
        default: "h-5 min-w-5 px-1.5 py-0.5 text-xs",
        lg: "h-5.5 min-w-5.5 px-1.5 py-0.5 text-xs",
        sm: "h-4.5 min-w-4.5 px-1 py-0.25 text-[0.625rem] leading-none",
        xl: "h-6 min-w-6 gap-1.5 px-2 py-0.75 text-sm",
        xs: "h-4 min-w-4 px-1 py-0.25 text-[0.6rem] leading-none",
      },
      variant: {
        default: "bg-primary text-primary-foreground",
        destructive: "bg-destructive text-destructive-foreground",
        "destructive-light":
          "border-destructive/20 bg-destructive/10 text-destructive-text",
        "destructive-outline":
          "border-border bg-background text-destructive-text",
        focus: "bg-primary text-primary-foreground",
        "focus-light": "border-primary/20 bg-primary/10 text-primary",
        "focus-outline": "border-border bg-background text-primary",
        info: "bg-info text-info-foreground",
        "info-light": "border-info/20 bg-info/10 text-info-text",
        "info-outline": "border-border bg-background text-info-text",
        invert: "bg-foreground text-background",
        "invert-light": "border-foreground/20 bg-foreground/10 text-foreground",
        "invert-outline": "border-border bg-background text-foreground",
        outline: "border-border bg-transparent text-foreground",
        "primary-light": "border-primary/20 bg-primary/10 text-primary",
        "primary-outline": "border-border bg-background text-primary",
        secondary: "bg-secondary text-secondary-foreground",
        success: "bg-success text-success-foreground",
        "success-light": "border-success/20 bg-success/10 text-success-text",
        "success-outline": "border-border bg-background text-success-text",
        warning: "bg-warning text-warning-foreground",
        "warning-light": "border-warning/20 bg-warning/10 text-warning-text",
        "warning-outline": "border-border bg-background text-warning-text",
      },
    },
  }
);

type BadgeProps = useRender.ComponentProps<"span"> &
  VariantProps<typeof badgeVariants>;

const Badge = ({
  className,
  radius = "default",
  ref,
  render,
  size = "default",
  variant = "default",
  ...props
}: BadgeProps) => {
  const defaultProps = {
    className: cn(badgeVariants({ radius, size, variant }), className),
    "data-radius": radius,
    "data-size": size,
    "data-slot": "badge",
    "data-variant": variant,
  };
  return useRender({
    defaultTagName: "span",
    props: mergeProps<"span">(defaultProps, props),
    ref,
    render,
  });
};

export { Badge, badgeVariants };
export type { BadgeProps };

```

## Credits [#credits]

* [ReUI](https://reui.io/docs/components/base/badge): Badge implementation adapted under the MIT license. Vandor supplies token-based radius, semantic foreground/text colors, public ref handling, and portable registry packaging. The full license notice is retained in the distributed source.
