Badge

A compact label for status, category or count.

import { Badge } from "@delacour/native-ui/badge";
Variants and colours
Variants and colours
import { BADGE_COLORS, BADGE_VARIANTS, Badge } from "@delacour/native-ui/badge";import { Text } from "@delacour/native-ui/text";import type { ReactElement } from "react";import { View } from "react-native";/** * Every colour of one variant, on a row that wraps. * * The grid is built from the exported `as const` arrays rather than written out, * so a colour added to `BADGE_COLORS` appears here with no edit. */function VariantRow({ variant }: { variant: (typeof BADGE_VARIANTS)[number] }): ReactElement {	return (		<View className="gap-2">			<Text.Caption color="muted">{variant}</Text.Caption>			<View className="flex-row flex-wrap gap-2">				{BADGE_COLORS.map((color) => (					<Badge color={color} key={color} variant={variant}>						{color}					</Badge>				))}			</View>		</View>	);}export function Demo(): ReactElement {	return (		<View className="gap-4">			{BADGE_VARIANTS.map((variant) => (				<VariantRow key={variant} variant={variant} />			))}		</View>	);}
<Badge color="success">Active</Badge>

Two axes, not one

variant says how the surface is painted — solid, soft, outline, ghost. color says what it means — default, primary, success, warning, danger, info. Sizes: sm, md, lg.

Button collapses the two into a single enum and a badge deliberately does not: six semantic colours are the point of this component rather than an afterthought, and one axis would need thirteen names to say what two say with ten.

Neither axis paints a surface alone, so all twenty-four pairings live in compoundVariants. A test asserts every cell is distinct — two cells collapsing means a caller can set an axis and see nothing change.

Content until given something to do

Status dot
Status dot
import { Badge } from "@delacour/native-ui/badge";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="flex-row flex-wrap gap-2">			<Badge color="success" variant="soft">				<Badge.StartContent>					<View className="size-1.5 rounded-full bg-success" />				</Badge.StartContent>				<Badge.Label>Online</Badge.Label>			</Badge>			<Badge color="warning" variant="soft">				<Badge.StartContent>					<View className="size-1.5 rounded-full bg-warning" />				</Badge.StartContent>				<Badge.Label>Away</Badge.Label>			</Badge>			<Badge variant="soft">				<Badge.StartContent>					<View className="size-1.5 rounded-full bg-muted-foreground" />				</Badge.StartContent>				<Badge.Label>Offline</Badge.Label>			</Badge>		</View>	);}

With no onPress and no onLongPress the root is a plain View. Mounting a GestureDetector regardless would put one under every tag in a list of fifty and announce each of them to assistive technology as a button with no action.

Supply either handler and the root becomes a Pressable, inheriting feedback, haptic and the rest; only the default differs — scale.

Dismissing

<Badge onClose={remove}>Design</Badge>

onClose is its own pressable — a Badge.CloseButton composed in at the end — so its tap is claimed by the inner detector and never also fires the badge's onPress. It presses with fade rather than the root's scale: a spring on a glyph that small reads as a jitter.

Reach for the part by hand only to place it somewhere other than last.

A size is padding, never a height

Sizes
Sizes
import { BADGE_SIZES, Badge } from "@delacour/native-ui/badge";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="flex-row flex-wrap items-center gap-2">			{BADGE_SIZES.map((size) => (				<Badge color="primary" key={size} size={size}>					size {size}				</Badge>			))}		</View>	);}

Text respects OS font scaling, so a fixed height clips the label at a large accessibility step instead of growing with it. A test asserts the root carries no h-* at any size.

self-start is load-bearing. A badge is sized by its content, and inside a gap column every child is stretch-aligned by default — without it a one-word badge spans the whole screen.

The border is reserved on every variant, transparent until outline colours it — declaring it only where it shows would make the badge two points wider the moment a caller switched variant.

Icons and text inherit

Composed icon
Composed icon
import { BADGE_SIZES, Badge } from "@delacour/native-ui/badge";import { Icon } from "@delacour/native-ui/icon";import { IconStar } from "@delacour/native-ui/icons/central";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="flex-row flex-wrap items-center gap-2">			{BADGE_SIZES.map((size) => (				<Badge color="warning" key={size} size={size} variant="soft">					<Icon icon={IconStar} />					<Badge.Label>Premium</Badge.Label>				</Badge>			))}		</View>	);}
Trailing icon, every variant
Trailing icon, every variant
import { BADGE_VARIANTS, Badge } from "@delacour/native-ui/badge";import { Icon } from "@delacour/native-ui/icon";import { IconCheckmark1Small } from "@delacour/native-ui/icons/central";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="flex-row flex-wrap gap-2">			{BADGE_VARIANTS.map((variant) => (				<Badge color="success" key={variant} variant={variant}>					<Badge.Label>Verified</Badge.Label>					<Icon icon={IconCheckmark1Small} />				</Badge>			))}		</View>	);}

The root wraps its subtree in an IconDefaultsProvider and a TextClassProvider, so a bare <Icon> or <Text> inside a badge comes out at the right size and colour with nothing said at the call site. String children are wrapped in a Badge.Label automatically.

API

A badge is a Pressable, so feedback, haptic, onPress and the rest are inherited rather than restated — see Pressable. asChild, busy and disabled are withheld: use isDisabled, which the parts read through context.

Prop

Type

Badge.Label

The badge's text. Picks its colour and type scale from the badge's variant, colour and size. Extends React Native's TextProps.

Prop

Type

Badge.CloseButton

The dismiss pressable. Defaults its accessibilityLabel to "Remove" and its feedback to fade — a badge that scaled on the close button's press would flex the whole chip. Extends PressableProps.

Prop

Type

Badge.StartContent / Badge.EndContent

Centred wrappers for leading and trailing content that is not an Icon. An Icon needs neither — it inherits the badge's icon size and its colour from the subtree.

Prop

Type

On this page