# Badge (/ui/components/react/badge)



<Playground component="badge" />

## Usage [#usage]

```tsx
import { Badge } from '@appica/ui-react/badge'
```

```tsx
<Badge variant="success">Active</Badge>
```

`Badge` is a compact, non-interactive label for a piece of metadata — a status, a count, a tag, a category. It renders a `<span>` by default and sizes to its content. Pick a `variant` for the color scheme and a `size` for the scale; drop an icon next to the text and mark it with `data-icon="start"` / `data-icon="end"` so the padding tightens on that side.

To make a badge clickable — a filter chip, a category link — pass a `render` element (an `<a>` or `<button>`). The badge then becomes focusable and gains hover/press affordances automatically. For a user-removable token with a built-in dismiss control, reach for [`Chip`](/ui/components/react/chip) instead.

## Examples [#examples]

### Default [#default]

A single `Badge` with text children uses the `primary` variant at the `md` size.

```tsx
import { Badge } from '@appica/ui-react/badge'

export default function BadgeDefault() {
  return <Badge>Badge</Badge>
}
```

### Variants [#variants]

Each `variant` carries a semantic color. The solid `primary`/`secondary` and tinted `error`/`success`/`warning`/`info` schemes draw attention to a status, while `soft`, `outline`, and `primary-outline` stay quieter for neutral metadata.

```tsx
import { Badge } from '@appica/ui-react/badge'

export default function BadgeVariants() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="primary">Primary</Badge>
      <Badge variant="primary-outline">Primary outline</Badge>
      <Badge variant="secondary">Secondary</Badge>
      <Badge variant="soft">Soft</Badge>
      <Badge variant="outline">Outline</Badge>
      <Badge variant="error">Error</Badge>
      <Badge variant="success">Success</Badge>
      <Badge variant="warning">Warning</Badge>
      <Badge variant="info">Info</Badge>
    </div>
  )
}
```

### On dark backgrounds [#on-dark-backgrounds]

The `light` variant is built for dark or photographic backgrounds — a translucent white fill that reads against the surface beneath it.

```tsx
import { Badge } from '@appica/ui-react/badge'
import { Crown } from '@appica/icons-react'

export default function BadgeOnDark() {
  return (
    <div className="relative flex h-44 w-full max-w-66 items-end justify-start overflow-hidden rounded-xl">
      <img src="/thumbnail.jpg" alt="" className="absolute inset-0 size-full object-cover" />
      <div className="absolute inset-0 bg-linear-to-t from-black/25 via-transparent to-transparent" />
      <Badge variant="light" className="relative m-3">
        <Crown data-icon="start" />
        Premium
      </Badge>
    </div>
  )
}
```

### Sizes [#sizes]

Four sizes scale the badge from `xs` to `lg`. The text and any icons scale together.

```tsx
import { Badge } from '@appica/ui-react/badge'

export default function BadgeSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge size="xs">Extra small</Badge>
      <Badge size="sm">Small</Badge>
      <Badge size="md">Medium</Badge>
      <Badge size="lg">Large</Badge>
    </div>
  )
}
```

### With an icon [#with-an-icon]

Place an icon before or after the text and tag it with `data-icon="start"` or `data-icon="end"` — the badge tightens the padding on that edge so the icon sits snug.

```tsx
import { Badge } from '@appica/ui-react/badge'
import { Check, Flame, BellRinging } from '@appica/icons-react'

export default function BadgeWithIcon() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="success">
        <Check data-icon="start" />
        Verified
      </Badge>
      <Badge variant="warning">
        <Flame data-icon="start" />
        Hot
      </Badge>
      <Badge variant="soft">
        Notification
        <BellRinging data-icon="end" />
      </Badge>
    </div>
  )
}
```

### Icon only [#icon-only]

The `icon-sm`, `icon-md`, and `icon-lg` sizes render a square badge for a lone icon. Give it an `aria-label` so the meaning is announced.

```tsx
import { Badge } from '@appica/ui-react/badge'
import { Check, Star, Bolt } from '@appica/icons-react'

export default function BadgeIconOnly() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="success" size="icon-sm" aria-label="Verified">
        <Check />
      </Badge>
      <Badge variant="primary-outline" size="icon-md" aria-label="Featured">
        <Star />
      </Badge>
      <Badge variant="warning" size="icon-lg" aria-label="Boosted">
        <Bolt />
      </Badge>
    </div>
  )
}
```

### Notification count [#notification-count]

Absolutely position a small badge against an icon button to surface an unread count. Keep the number in the badge and the count in its `aria-label`.

```tsx
import { Badge } from '@appica/ui-react/badge'
import { Button } from '@appica/ui-react/button'
import { Bell } from '@appica/icons-react'

export default function BadgeNotification() {
  return (
    <div className="relative inline-flex">
      <Button variant="outline" size="icon-md" aria-label="Notifications">
        <Bell />
      </Button>
      <Badge
        variant="secondary"
        size="xs"
        className="absolute -inset-e-1.5 -top-1.5 min-w-4 px-1 font-medium"
        aria-label="3 unread notifications"
      >
        3
      </Badge>
    </div>
  )
}
```

### Interactive (link or button) [#interactive-link-or-button]

Pass a `render` element to turn the badge into a link — a plain `<a>` or a framework one like a Next.js `<Link>` — or a `<button>`. It becomes focusable and animates on hover and press.

```tsx
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { Badge } from '@appica/ui-react/badge'

export default function BadgeInteractive() {
  const [following, setFollowing] = useState(false)

  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <Badge variant="soft" render={<a href="#design" />}>
        Design
      </Badge>
      <Badge variant="outline" render={<Link href="#trending" />}>
        Trending
      </Badge>
      <Badge
        variant={following ? 'primary' : 'primary-outline'}
        render={<button type="button" onClick={() => setFollowing((value) => !value)} />}
      >
        {following ? 'Following' : 'Follow'}
      </Badge>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The icon flips to the start (right) edge and the tightened padding follows, because both are expressed with logical properties. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="badge" />

## API reference [#api-reference]

`Badge` is built on Base UI's [`useRender`](https://base-ui.com/react/utils/use-render) and renders a `<span>` by default. Pass a `render` element to swap the underlying tag (e.g. an `<a>` or `<button>`), which also makes it focusable and interactive; `render` and its `state` (`{ variant, size }`) follow Base UI's render conventions. Every other `<span>` (or the rendered element's) attribute is forwarded.

| Prop        | <ColMinWidth width="200">Type</ColMinWidth>                                                                                                 | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                         |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------ |
| `variant`   | <Code>'primary' \| 'primary-outline' \| 'secondary' \| 'soft' \| 'outline' \| 'error' \| 'success' \| 'warning' \| 'info' \| 'light'</Code> | `'primary'` | Color scheme.                                                                              |
| `size`      | <Code>'xs' \| 'sm' \| 'md' \| 'lg' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'</Code>                                                            | `'md'`      | Scale. The `icon-*` sizes render a square badge for a lone icon.                           |
| `children`  | `ReactNode`                                                                                                                                 | —           | The label content. Tag an icon with `data-icon="start"`/`"end"` to tighten that edge.      |
| `render`    | <Code>ReactElement \| ((props, state) => ReactElement)</Code>                                                                               | —           | Replace the `<span>` with another element. Doing so makes the badge focusable/interactive. |
| `className` | `string`                                                                                                                                    | —           | Extra classes, merged via `tailwind-merge`.                                                |

## Accessibility [#accessibility]

* A plain `Badge` is a `<span>` — purely visual. Don't rely on color alone to convey meaning; back it with text (e.g. "Active", not just a green badge).
* For an **icon-only** badge, supply an `aria-label` so the icon's meaning is announced.
* For a **count** badge, keep the visible number short and put the full meaning in `aria-label` (e.g. `"3 unread notifications"`).
* When you make a badge interactive via `render`, render a real `<a>` or `<button>` so it's keyboard-focusable and announced with the correct role; the badge applies focusable styling automatically.
* Hover/press transitions are skipped when the user prefers reduced motion.
