Badge
Small status label with Variants default, secondary, outline, destructive, success and warning.
Componentdisplay
Style
Light
Dark
Your app is ready.
Inter body text, Inter headings.
smmdlgxl
you@example.com
Continue
a0: Vega, neutral, neutral accent, default radius, Inter.Change PresetInstallation
$
npx nativecn-cli@latest add badgeUsage
Badge demo
A Badge in every Variant.
import { View } from "react-native";
import { Badge } from "@/components/badge";
import { createStyles } from "@/theme";
export default function BadgeDemo() {
const styles = useStyles();
return (
<View style={styles.row}>
<Badge label="New" />
<Badge label="Beta" variant="secondary" />
<Badge label="Draft" variant="outline" />
<Badge label="Failed" variant="destructive" />
<Badge label="Paid" variant="success" />
<Badge label="Pending" variant="warning" />
</View>
);
}
const useStyles = createStyles((t) => ({
row: { flexDirection: "row", flexWrap: "wrap", gap: t.spacing[2] },
}));Badge demo
A Badge in every Variant.
import { View } from "react-native";
import { Badge } from "@/components/badge";
import { createStyles } from "@/theme";
export default function BadgeDemo() {
const styles = useStyles();
return (
<View style={styles.row}>
<Badge label="New" />
<Badge label="Beta" variant="secondary" />
<Badge label="Draft" variant="outline" />
<Badge label="Failed" variant="destructive" />
<Badge label="Paid" variant="success" />
<Badge label="Pending" variant="warning" />
</View>
);
}
const useStyles = createStyles((t) => ({
row: { flexDirection: "row", flexWrap: "wrap", gap: t.spacing[2] },
}));Props
| Prop | Type and description |
|---|---|
label | string (required): the visible text and accessible name |
variant | "default" (default) | "secondary" | "outline" | "destructive" | "success" | "warning" |
style | merged last |
Variants
default,secondary,outline,destructive,success,warning
Accessibility
- Not interactive: its label is read as plain text.
- The label uses the caption text Variant, which caps the OS font scale at 1.5× when font scaling is on.
Notes
Not pressable: for a selectable or removable pill use Chip. A Badge sizes to its label (alignSelf flex-start). Its size comes from the badge.root and badge.label Style Slots.
Source
The Vega Style's code, exactly as add copies it into your app.
{components}/badge.tsx
import type { Ref } from "react";
import { View, type ViewProps } from "react-native";
import { Text, type TextColor } from "@/components/text";
import { createStyles, type ColorRole } from "@/theme";
/** Each Variant's Colour Roles: fill, content and (optional) border. */
const variants = {
default: { background: "primary", foreground: "primaryForeground" },
secondary: { background: "secondary", foreground: "secondaryForeground" },
outline: { foreground: "foreground", border: "border" },
destructive: { background: "destructive", foreground: "destructiveForeground" },
success: { background: "success", foreground: "successForeground" },
warning: { background: "warning", foreground: "warningForeground" },
} as const satisfies Record<
string,
{ background?: ColorRole; foreground: TextColor; border?: ColorRole }
>;
export type BadgeVariant = keyof typeof variants;
export type BadgeProps = Omit<ViewProps, "children"> & {
/** The visible text, which is also the accessible name. */
label: string;
/** Default `default`. */
variant?: BadgeVariant;
ref?: Ref<View>;
};
const useStyles = createStyles((t) => ({
root: {
...{
height: t.scaleValue(22),
borderRadius: t.radius.sm,
paddingHorizontal: t.spacing[2],
},
flexDirection: "row",
alignItems: "center",
alignSelf: "flex-start",
borderCurve: "continuous",
borderWidth: t.borderWidth.default,
borderColor: "transparent",
},
label: { ...t.type.caption, fontFamily: t.type.label.fontFamily },
}));
const useVariantStyles = createStyles((t) => ({
default: { backgroundColor: t.colors.primary },
secondary: { backgroundColor: t.colors.secondary },
outline: { backgroundColor: "transparent", borderColor: t.colors.border },
destructive: { backgroundColor: t.colors.destructive },
success: { backgroundColor: t.colors.success },
warning: { backgroundColor: t.colors.warning },
}));
/**
* A small status or count label. Not pressable; for a pressable or selectable pill use Chip.
* Its `label` is the accessible name. `style` is merged last onto the root.
*/
export function Badge({ label, variant = "default", style, ...props }: BadgeProps) {
const styles = useStyles();
const variantStyles = useVariantStyles();
return (
<View style={[styles.root, variantStyles[variant], style]} {...props}>
<Text
variant="caption"
color={variants[variant].foreground}
numberOfLines={1}
style={styles.label}
>
{label}
</Text>
</View>
);
}{components}/badge.tsx
import type { Ref } from "react";
import { View, type ViewProps } from "react-native";
import { Text, type TextColor } from "@/components/text";
import { createStyles, type ColorRole } from "@/theme";
/** Each Variant's Colour Roles: fill, content and (optional) border. */
const variants = {
default: { background: "primary", foreground: "primaryForeground" },
secondary: { background: "secondary", foreground: "secondaryForeground" },
outline: { foreground: "foreground", border: "border" },
destructive: { background: "destructive", foreground: "destructiveForeground" },
success: { background: "success", foreground: "successForeground" },
warning: { background: "warning", foreground: "warningForeground" },
} as const satisfies Record<
string,
{ background?: ColorRole; foreground: TextColor; border?: ColorRole }
>;
export type BadgeVariant = keyof typeof variants;
export type BadgeProps = Omit<ViewProps, "children"> & {
/** The visible text, which is also the accessible name. */
label: string;
/** Default `default`. */
variant?: BadgeVariant;
ref?: Ref<View>;
};
const useStyles = createStyles((t) => ({
root: {
...{
height: t.scaleValue(20),
borderRadius: t.scaleValue(4),
paddingHorizontal: t.scaleValue(6),
},
flexDirection: "row",
alignItems: "center",
alignSelf: "flex-start",
borderCurve: "continuous",
borderWidth: t.borderWidth.default,
borderColor: "transparent",
},
label: {
...t.type.caption,
fontFamily: t.type.label.fontFamily,
fontSize: t.scaleValue(11),
lineHeight: t.scaleValue(14),
},
}));
const useVariantStyles = createStyles((t) => ({
default: { backgroundColor: t.colors.primary },
secondary: { backgroundColor: t.colors.secondary },
outline: { backgroundColor: "transparent", borderColor: t.colors.border },
destructive: { backgroundColor: t.colors.destructive },
success: { backgroundColor: t.colors.success },
warning: { backgroundColor: t.colors.warning },
}));
/**
* A small status or count label. Not pressable; for a pressable or selectable pill use Chip.
* Its `label` is the accessible name. `style` is merged last onto the root.
*/
export function Badge({ label, variant = "default", style, ...props }: BadgeProps) {
const styles = useStyles();
const variantStyles = useVariantStyles();
return (
<View style={[styles.root, variantStyles[variant], style]} {...props}>
<Text
variant="caption"
color={variants[variant].foreground}
numberOfLines={1}
style={styles.label}
>
{label}
</Text>
</View>
);
}
