Skyroc Native UI

Badge

标记数量或状态的角标组件

角标(Badge)用于在图标、头像等内容的角落标记未读数量或状态。传 children 时角标绝对定位在内容的某个角落,省略 children 则独立成块渲染。角标外圈带一圈与页面底色同色的描边,压在内容上时有一圈"挖空"效果。

import { Badge } from '@skyroc/native-ui';

基础用法

content 传数字或文字,dot 切换为不带内容的小圆点。

BadgeBasic.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const BadgeBasic = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      <Sample label="数字">
        <Badge content={5}>
          <DemoTarget label="A" />
        </Badge>
      </Sample>
      <Sample label="自动封顶">
        <Badge content={200}>
          <DemoTarget label="B" />
        </Badge>
      </Sample>
      <Sample label="文字">
        <Badge content="new">
          <DemoTarget label="C" />
        </Badge>
      </Sample>
      <Sample label="圆点">
        <Badge dot>
          <DemoTarget label="D" />
        </Badge>
      </Sample>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeBasic };

何时使用

  • 标记未读消息数、待处理数量等可数信息,用 content
  • 只需要提示"有更新"而不关心具体数量时,用 dot——它比一个数字更轻,也不会因为位数变化撑宽布局。
  • 需要一枚可点击、带文字语义的标签(分类、状态标签)时用 TagBadge 不响应交互。

数字封顶

content 为数字且大于 max 时显示 {max}+,默认 max 为 99。封顶只对数字生效,字符串 content 原样展示。

BadgeMax.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const BadgeMax = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      <Sample label="content=99">
        <Badge content={99}>
          <DemoTarget label="A" />
        </Badge>
      </Sample>
      <Sample label="content=100">
        <Badge content={100}>
          <DemoTarget label="B" />
        </Badge>
      </Sample>
      <Sample label="max=9">
        <Badge
          content={10}
          max={9}
        >
          <DemoTarget label="C" />
        </Badge>
      </Sample>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeMax };

空值与零值

contentundefinednull 或空字符串时角标不渲染;为 0 时默认也不渲染,需要保留一个"0"时传 showZerodot 优先级最高,开启后忽略 contentshowZero,始终显示。

BadgeShowZero.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const BadgeShowZero = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      <Sample label="undefined">
        <Badge content={undefined}>
          <DemoTarget label="A" />
        </Badge>
      </Sample>
      <Sample label="空字符串">
        <Badge content="">
          <DemoTarget label="B" />
        </Badge>
      </Sample>
      <Sample label="content=0">
        <Badge content={0}>
          <DemoTarget label="C" />
        </Badge>
      </Sample>
      <Sample label="showZero">
        <Badge
          showZero
          content={0}
        >
          <DemoTarget label="D" />
        </Badge>
      </Sample>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeShowZero };

语义颜色

color 提供 6 种语义色,文字角标与圆点角标共用同一份取色,切到 dot 不会丢掉颜色语义:

颜色语义适用场景
destructive强提醒未读消息、错误数(默认)
primary主题与主色一致的常规标记
secondary次级弱化的辅助标记
success成功已完成、在线
warning警告需要注意的数量
info信息中性的信息提示
BadgeColor.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const COLORS = ['destructive', 'primary', 'secondary', 'success', 'warning', 'info'] as const;

const BadgeColor = () => {
  return (
    <View className="bg-background p-4">
      <Text className="mb-3 text-sm font-medium text-foreground">文字角标</Text>
      <View className="flex-row flex-wrap gap-x-3 gap-y-5">
        {COLORS.map(color => (
          <Sample
            key={color}
            label={color}
          >
            <Badge
              color={color}
              content={6}
            >
              <DemoTarget label="A" />
            </Badge>
          </Sample>
        ))}
      </View>

      <Text className="mb-3 mt-6 text-sm font-medium text-foreground">圆点角标</Text>
      <View className="flex-row flex-wrap gap-x-3 gap-y-5">
        {COLORS.map(color => (
          <Sample
            key={color}
            label={color}
          >
            <Badge
              dot
              color={color}
            >
              <DemoTarget label="A" />
            </Badge>
          </Sample>
        ))}
      </View>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeColor };

尺寸

size 同时决定角标高度、最小宽度、字号与圆点直径:

尺寸高度 / 最小宽度横向内边距字号 / 行高圆点直径
sm16210 / 126
md20(默认)412 / 148
lg24614 / 1610
BadgeSize.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const SIZES = ['sm', 'md', 'lg'] as const;

const BadgeSize = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      {SIZES.map(size => (
        <Sample
          key={size}
          label={`${size} 数值`}
        >
          <Badge
            content={8}
            size={size}
          >
            <DemoTarget label="A" />
          </Badge>
        </Sample>
      ))}
      {SIZES.map(size => (
        <Sample
          key={`dot-${size}`}
          label={`${size} 圆点`}
        >
          <Badge
            dot
            size={size}
          >
            <DemoTarget label="A" />
          </Badge>
        </Sample>
      ))}
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeSize };

min-w 与高度相等,保证单字符角标是正圆;行高小于高度减去描边,文字才能被垂直居中。自定义 classNames.content 的字号时,注意别让行高超过角标高度,否则文字会把角标撑高。

挂载位置

position 决定角标挂在 children 的哪个角落,默认 top-right。角标会按自身尺寸的 50% 向外推,骑在内容边角上。

BadgePosition.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const POSITIONS = ['top-right', 'top-left', 'bottom-right', 'bottom-left'] as const;

const BadgePosition = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      {POSITIONS.map(position => (
        <Sample
          key={position}
          label={position}
        >
          <Badge
            content={3}
            position={position}
          >
            <DemoTarget label="A" />
          </Badge>
        </Sample>
      ))}
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgePosition };

位置偏移

offsetposition 的默认位置上叠加像素微调 [x, y],x 向右为正、y 向下为正。它是在 50% 外推之上再叠一层位移,不会替换默认定位。

BadgeOffset.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const BadgeOffset = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      <Sample label="默认">
        <Badge content={3}>
          <DemoTarget label="A" />
        </Badge>
      </Sample>
      <Sample label="[-6, 6]">
        <Badge
          content={3}
          offset={[-6, 6]}
        >
          <DemoTarget label="B" />
        </Badge>
      </Sample>
      <Sample label="[6, -6]">
        <Badge
          content={3}
          offset={[6, -6]}
        >
          <DemoTarget label="C" />
        </Badge>
      </Sample>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeOffset };

独立使用

省略 children 后角标独立成块渲染。此时角标本身就是根节点:classNamestyle 以及其余 View 属性都直接作用于角标(或圆点),position / offset 不再生效——没有被标记的内容,也就无所谓角落。

BadgeStandalone.tsx
Loading…
import { Badge } from '@skyroc/native-ui';
import { View } from 'react-native';

const BadgeStandalone = () => {
  return (
    <View className="flex-row flex-wrap items-center gap-4 bg-background p-4">
      <Badge content={12} />
      <Badge
        color="success"
        content="OK"
      />
      <Badge
        dot
        color="warning"
      />
      <Badge
        className="border-0"
        color="info"
        content={666}
      />
    </View>
  );
};

export { BadgeStandalone };

独立使用时那圈 border-background 通常是多余的,可以用 className="border-0" 去掉。

自定义内容与样式

contentReactElement 时原样渲染,不再包裹 Text,也不参与 max 封顶——此时字号、颜色都由你自己的节点决定。classNames 按 slot 细粒度覆盖:

slot作用位置
root包裹 children 的相对定位容器(独立模式不存在)
badge文字角标的圆角容器
content角标内自动包裹的 Text
dot圆点角标
BadgeStyles.tsx
Loading…
import { Badge, Text } from '@skyroc/native-ui';
import type { ReactNode } from 'react';
import { View } from 'react-native';

const BadgeStyles = () => {
  return (
    <View className="flex-row flex-wrap gap-x-3 gap-y-5 bg-background p-4">
      <Sample label="ReactElement">
        <Badge content={<Text className="px-1 text-2xs font-bold text-destructive-foreground">HOT</Text>}>
          <DemoTarget label="A" />
        </Badge>
      </Sample>
      <Sample label="badge / content">
        <Badge
          classNames={{ badge: 'rounded-md', content: 'font-normal' }}
          color="info"
          content={9}
        >
          <DemoTarget label="B" />
        </Badge>
      </Sample>
      <Sample label="dot">
        <Badge
          dot
          classNames={{ dot: 'h-3 w-3' }}
          color="success"
        >
          <DemoTarget label="C" />
        </Badge>
      </Sample>
      <Sample label="root">
        <Badge
          classNames={{ root: 'rounded-xl bg-muted p-2' }}
          content={2}
        >
          <DemoTarget label="D" />
        </Badge>
      </Sample>
    </View>
  );
};

interface DemoTargetProps {
  /** 用于区分示例目标的简短标记 */
  label: string;
}

/** 模拟头像、图标等被角标标记的内容 */
const DemoTarget = (props: DemoTargetProps) => {
  const { label } = props;

  return (
    <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
      <Text className="text-sm font-semibold text-muted-foreground">{label}</Text>
    </View>
  );
};

interface SampleProps {
  /** 当前示例内容 */
  children: ReactNode;

  /** 示例下方的 API 值说明 */
  label: string;
}

const Sample = (props: SampleProps) => {
  const { children, label } = props;

  return (
    <View className="w-20 items-center gap-2">
      {children}
      <Text className="text-center text-xs text-muted-foreground">{label}</Text>
    </View>
  );
};

export { BadgeStyles };

包裹模式下 className 合并到 root;独立模式下没有 rootclassName 改为合并到角标自身。

文字子节点

childrenstring / number 时自动用组件库的 Text 包裹,其他节点保持原样,不做额外包装。

BadgeTextChildren.tsx
Loading…
import { Badge } from '@skyroc/native-ui';
import { View } from 'react-native';

const BadgeTextChildren = () => {
  return (
    <View className="flex-row flex-wrap items-center gap-8 bg-background p-4">
      <Badge content={1}>消息</Badge>
      <Badge dot>动态</Badge>
      <Badge content="new">7</Badge>
    </View>
  );
};

export { BadgeTextChildren };

无障碍

角标文字关掉了 Android 的 includeFontPadding(该属性在 iOS 上被忽略),避免字形上下的额外空白把数字顶偏。角标本身没有默认的 accessibilityRole:读屏场景下建议在被标记的内容(按钮、Tab)上写完整的 accessibilityLabel,例如"消息,3 条未读",而不是让读屏器单独播报一个数字。

API

Badge

除下表外,Badge 透传 View 的全部属性(styletestID 等)。包裹模式下它们落在外层容器上,独立模式下直接落在角标上。

属性说明类型默认值
content角标内容,number / string 自动包裹 Text,ReactElement 原样渲染ReactNode-
dot只渲染一个小圆点,忽略 content 与 showZerobooleanfalse
children被角标标记的内容,省略时角标独立成块渲染;string / number 会被自动包裹为 TextReactNode-
color语义颜色,同时作用于文字角标与圆点'primary' | 'destructive' | 'secondary' | 'success' | 'warning' | 'info''destructive'
size尺寸,同时决定角标高度、最小宽度、字号与圆点直径'sm' | 'md' | 'lg''md'
max数字封顶值,content 为数字且超过时显示 {max}+number99
showZerocontent 为 0 时是否仍然展示角标booleanfalse
position包裹 children 时角标挂载的角落,独立模式下不生效'top-right' | 'top-left' | 'bottom-right' | 'bottom-left''top-right'
offset在默认角落位置上做像素微调 [x, y],x 向右为正、y 向下为正,独立模式下不生效[number, number]-
className包裹模式合并到 root slot,独立模式合并到角标自身string-
classNames各 slot 的类名覆盖SlotClassNames<'badge' | 'content' | 'dot' | 'root'>-
ref底层 View 的 ref,用于 measure / 滚动定位等命令式操作Ref<View>-

类型

import type { BadgeColor, BadgePosition, BadgeProps, BadgeSize, BadgeSlots } from '@skyroc/native-ui';

BadgeColor

角标语义颜色,文字角标与圆点共用同一份取色。

'primary' | 'destructive' | 'secondary' | 'success' | 'warning' | 'info'

BadgeSize

角标尺寸,同时决定高度、最小宽度、字号与圆点直径。

'sm' | 'md' | 'lg'

BadgePosition

角标相对 children 的挂载角落,独立模式下不生效。

'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'

BadgeSlots

可通过 classNames 覆盖的 slot 名称。

'badge' | 'content' | 'dot' | 'root'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 BadgeSlots。

Partial<Record<Slots, string>>