Display

Badge

A short label for a status, category or count: "Draft", "Vegan", "3". With href it becomes a link and with onClick a button, with press feedback and a 44px touch target on phones, the same way Button does. Without onClick or href a Badge is not a button: it is a static <span>, and disabled and type do nothing.

DraftPublishedBraise

In Spoonjoy

Spoonjoy, a recipe app, is built with Ouro HX (how).

See it live, no sign-in needed: Recipes · Search · Developers

In its source: app/components/recipe/CoverProvenanceBadge.tsx · app/components/recipe/RecipeHeader.tsx · app/routes/developers.playground.tsx

Install

Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):

npx shadcn@latest init --base base --preset nova --yes --no-monorepo
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13

Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):

npx shadcn@latest add ourostack/ouro-ui/badge#v1.2.13

Or, with the @ouro namespace set up:

npx shadcn@latest add @ouro/ouro-base @ouro/badge

Its tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):

npx shadcn@latest add ourostack/ouro-ui/badge#v1.2.13 ourostack/ouro-ui/badge-tests#v1.2.13
import { Badge } from "@/components/ouro/badge/badge";

Paths use shadcn's default @/ alias; the CLI rewrites them to yours.

Example

<Badge>Draft</Badge>
<Badge tone="success" variant="soft">Published</Badge>
<Badge href="/tags/vegan">Vegan</Badge>
<Badge onClick={toggle} aria-pressed={active}>Quick</Badge>

Use instead when

  • Use Button instead for an action that needs a clear call to action, and Alert for a message the reader must notice.

Props

The props the modes share. Type: BadgeProps.

children (required)
Type: ReactNode
The label: a short word, or a count.
className
Type: string
Layout only: margins, grid placement (ms-2). Appearance classes warn in development; use tone and variant.
icon
Type: BadgeIcon
Icon before the label. Pass the component, not an element: icon={CheckIcon}.
onClick
Type: MouseEventHandler<HTMLElement>
Runs when the badge is pressed. Without href it makes the badge a <button> (pass aria-pressed too when it toggles a filter); with href it runs before the link navigates.
tone
Type: "primary" | "secondary" | "destructive" | "success" | "warning"
Default: "secondary"
Color role from the theme, named after its token. secondary is a quiet neutral label; use success, warning and destructive for status. shadcn's variant="destructive" is tone="destructive".
variant
Type: "solid" | "soft" | "outline"
Default: "solid"
Visual weight: solid fills the badge with the tone, soft tints it, outline draws only the border. shadcn's default is tone="primary", its secondary and destructive are tones, and ghost is soft.

Modes

BadgeProps is a union: pass the props of exactly one mode. Mixing them is a type error.

With href

href (required)
Type: string
Where the link goes. Internal paths render through OuroProvider's linkComponent (your router's link); absolute URLs render as a plain <a>, and with target="_blank" always get rel="noopener noreferrer".
ref
Type: Ref<HTMLAnchorElement>
Reaches the rendered element.

Also accepts the attributes in React's AnchorHTMLAttributes and HTMLAttributes (id, aria-*, event handlers), passed to the rendered element, except those named on this page.

Not in this mode (typed never):

  • disabled: A link cannot be disabled. Remove href and pass onClick for a button.
  • type: Links have no type. Remove href and pass onClick for a button.

With type

ref
Type: Ref<HTMLButtonElement & HTMLSpanElement>
Reaches the rendered element.
type
Type: "button" | "submit" | "reset"
Default: "button"
With onClick, the button's role in a form.

Also accepts the attributes in React's ButtonHTMLAttributes and HTMLAttributes (id, aria-*, event handlers), passed to the rendered element, except those named on this page.

Not in this mode (typed never):

  • href: Not set: a static badge or, with onClick, a button. Pass href for a link.

Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

  • asChild: Not a prop. To render a link, pass href: <Badge href="/tags/vegan">.
  • color: Not a prop. Ouro UI uses tone: <Badge tone="destructive">.

className is for layout only

Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: Badge merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.

Alignment classes (text-center) warn too: set alignment on a parent element.

Types

BadgeIcon

An icon component, such as one from lucide-react or @heroicons/react.

type BadgeIcon = ComponentType<SVGProps<SVGSVGElement>>;

Claim sources