# Badge

> A count, dot or status badge that springs in when it appears, pops when its value changes, caps at 99+ and shrinks away at zero; BadgeAnchor pins it to an icon or avatar.

- Type: Component (feedback)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/badge
- Markdown: https://goodui.dev/native/components/badge.md
- Registry item: https://goodui.dev/native/r/badge.json
- Dependencies: react-native-reanimated
- Keywords: react native badge, notification badge, unread count, badge 99+, status dot, online indicator, avatar badge, expo badge

## When to use

- Unread or pending counts on a tab, an icon or a list row.
- A dot that says something is new without a number.
- Short status next to a title: New, Live, Pro, Beta, Overdue.
- An online or live indicator on an avatar (dot with pulse).

## When not to use

- Selectable or removable tags: use chip.
- Numbers that are the content itself, such as a balance or a price: use rolling-number.
- Messages the person must read: use toast or inline text.
- Counts on an icon-only button: pass `badge` to icon-button, which also puts the count in the button's label.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/badge.json
npx expo install --fix
```

The component lands in `components/goodui-native/badge/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { View } from "react-native";
import { Text } from "@/components/goodui-native/foundation";
import { Badge, BadgeAnchor } from "@/components/goodui-native/badge/badge";

export function InboxRow({ unread, avatar }: { unread: number; avatar: React.ReactNode }) {
  return (
    <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
      <BadgeAnchor dot pulse tone="success" overlap="circular" placement="bottom-right" accessibilityLabel="Online">
        {avatar}
      </BadgeAnchor>
      <Text style={{ flex: 1 }}>Design team</Text>
      <Badge count={unread} />
    </View>
  );
}
```

## Examples

### Counts, caps and zero

```tsx
<Badge count={3} />
<Badge count={128} />            // "99+"
<Badge count={1200} max={999} /> // "999+"
<Badge count={0} />              // hidden; shrinks away when it reaches 0
<Badge count={0} showZero tone="neutral" variant="soft" />
```

### Status labels

```tsx
<Badge label="New" tone="accent" variant="soft" />
<Badge label="Live" dot pulse tone="danger" variant="soft" />
<Badge label="Overdue" tone="warning" variant="outline" size="sm" />
```

### On an icon

```tsx
<BadgeAnchor count={unread} ring="background">
  <BellIcon />
</BadgeAnchor>
```

## API reference

### Badge

`import { Badge } from "@/components/goodui-native/badge/badge";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `count` | `number` | – | A count. Hidden at 0 unless `showZero`; above `max` it reads "99+". |
| `max` | `number` | `99` | Highest count shown as a number. Default 99. |
| `showZero` | `boolean` | `false` | Keep the badge visible with a count of 0. |
| `label` | `string` | – | Status text such as "New", "Live" or "Pro". Takes the place of the count. |
| `dot` | `boolean` | `false` | A dot instead of the number (shown while `count` is above 0, or always without a count). With `label`, a leading status dot. |
| `pulse` | `boolean` | `false` | The dot breathes outward, for live or online states. Off under reduced motion. |
| `variant` | `"solid" \| "soft" \| "outline"` | `"solid"` | `solid` for counts that need attention, `soft` and `outline` for quiet status. |
| `tone` | `"success" \| "warning" \| "danger" \| "accent" \| "neutral"` | `"danger"` | `danger` for unread counts; `accent`, `neutral` or a status for everything else. |
| `size` | `"sm" \| "md"` | `"md"` | `sm` is 16 pt tall (dot 8), `md` 20 pt (dot 10). |
| `ring` | `BadgeRing \| false` | `false` | Draws a 2 pt ring in this surface color, so the badge reads as cut out of what it sits on. |
| `decorative` | `boolean` | `false` | Hide the badge from screen readers because the element it belongs to already says the count in its label. |
| `accessibilityLabel` | `string` | – | Read by screen readers. Defaults to the label, the count ("More than 99") or "New". |
| `labels` | `Partial<BadgeLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

### BadgeAnchor

`import { BadgeAnchor } from "@/components/goodui-native/badge/badge";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accessibilityLabel` | `string` | – | Read by screen readers. Defaults to the label, the count ("More than 99") or "New". |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `count` | `number` | – | A count. Hidden at 0 unless `showZero`; above `max` it reads "99+". |
| `max` | `number` | – | Highest count shown as a number. Default 99. |
| `showZero` | `boolean` | – | Keep the badge visible with a count of 0. |
| `label` | `string` | – | Status text such as "New", "Live" or "Pro". Takes the place of the count. |
| `dot` | `boolean` | – | A dot instead of the number (shown while `count` is above 0, or always without a count). With `label`, a leading status dot. |
| `pulse` | `boolean` | – | The dot breathes outward, for live or online states. Off under reduced motion. |
| `variant` | `"solid" \| "soft" \| "outline"` | – | `solid` for counts that need attention, `soft` and `outline` for quiet status. |
| `tone` | `"success" \| "warning" \| "danger" \| "accent" \| "neutral"` | – | `danger` for unread counts; `accent`, `neutral` or a status for everything else. |
| `size` | `"sm" \| "md"` | `"sm"` | `sm` is 16 pt tall (dot 8), `md` 20 pt (dot 10). |
| `ring` | `BadgeRing \| false` | `"surface"` | Draws a 2 pt ring in this surface color, so the badge reads as cut out of what it sits on. |
| `decorative` | `boolean` | – | Hide the badge from screen readers because the element it belongs to already says the count in its label. |
| `labels` | `Partial<BadgeLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `children` (required) | `ReactNode` | – | The icon, avatar or button the badge belongs to. |
| `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | Which corner the badge sits on. Its center lands on that corner. Mirrored under RTL (right means the end side). |
| `overlap` | `"rectangular" \| "circular"` | `"rectangular"` | `circular` pulls the badge in to sit on the edge of a round child such as an avatar. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

## Accessibility

- Read as text: the label, the count, or 'More than 99' past the cap; override with accessibilityLabel.
- Pass `decorative` when the element it belongs to already says the count, so it is not read twice.
- The badge never takes touches; BadgeAnchor's child keeps its own role and label.
- Status meaning never relies on color alone: pair a tone with a label or with the anchored element's label.
- On the web a badge is role img named by its label, and a decorative badge is aria-hidden.

## Motion

- Appears with the bouncy spring from zero scale; at zero (or when its content goes away) it shrinks out with a fast timing, then unmounts.
- A changed count or label pops: a quick swell to 1.3, then the bouncy spring back.
- `pulse` sends a ring outward from the dot every 1.4 s, for live and online states.
- Reduced motion: no pop, no scale and no pulse; the badge fades in and out.

## Haptics

- None: a badge reports a change, it does not confirm an action.

## Performance

- All animation runs on the UI thread. React renders only when the count, label or visibility changes.
- BadgeAnchor measures its child once per real size change (for circular overlap) and never re-lays it out.

## Notes for AI

- Default tone is danger with the solid variant: the unread count look. Use tone='accent' or 'neutral' with variant='soft' for status that is not urgent.
- Pass a number to count, not a string; the cap is max (default 99) and renders as '99+'.
- A badge with count 0 hides itself unless showZero is set. Keep it mounted and pass 0 so it can animate out.
- BadgeAnchor hugs its child (alignSelf flex-start). Use overlap='circular' for round avatars and a ring token that matches the surface behind the child.
- dot alone shows a dot (while count > 0 if a count is given); dot with label shows a status dot before the text.

## Related

- [Icon button](https://goodui.dev/native/components/icon-button.md): A layered icon-only button whose surface and icon sink on touch down and release on different springs; it runs async actions itself, carries a badge and shows a hint on long press.
- [Chip](https://goodui.dev/native/components/chip.md): Filter, choice and input chips: selecting one fills it with the accent while a check draws itself in, removing one shrinks it away, and a ChipGroup in single mode moves one elastic highlight from chip to chip.
- [Rolling number](https://goodui.dev/native/components/rolling-number.md): A number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.

## Guidance for AI tools

Badge: A count, dot or status badge that springs in when it appears, pops when its value changes, caps at 99+ and shrinks away at zero; BadgeAnchor pins it to an icon or avatar. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
