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.
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-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then 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.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/badgeIts 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.13import { 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
Buttoninstead for an action that needs a clear call to action, andAlertfor 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; usetoneandvariant. 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
hrefit makes the badge a<button>(passaria-pressedtoo when it toggles a filter); withhrefit runs before the link navigates. tone- Type:
"primary" | "secondary" | "destructive" | "success" | "warning" - Default:
"secondary" - Color role from the theme, named after its token.
secondaryis a quiet neutral label; usesuccess,warninganddestructivefor status. shadcn'svariant="destructive"istone="destructive". variant- Type:
"solid" | "soft" | "outline" - Default:
"solid" - Visual weight:
solidfills the badge with the tone,softtints it,outlinedraws only the border. shadcn'sdefaultistone="primary", itssecondaryanddestructiveare tones, andghostissoft.
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'slinkComponent(your router's link); absolute URLs render as a plain<a>, and withtarget="_blank"always getrel="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. Removehrefand passonClickfor a button.type: Links have notype. Removehrefand passonClickfor 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, withonClick, a button. Passhreffor 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, passhref:<Badge href="/tags/vegan">.color: Not a prop. Ouro UI usestone:<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
- layout-classes: test:registry/ouro/components/ouro/badge/badge.test.tsx::Badge > forwards a ref and lets app layout classes win
verify:testExists(registry/ouro/components/ouro/badge/badge.test.tsx::Badge > forwards a ref and lets app layout classes win)