wui
组件

头像 Avatar

用于展示用户、团队或机器人身份的图形元素,内置图片加载回退、在线状态徽章与多头像重叠组能力。

第三方依赖 · radix-ui第三方依赖 · class-variance-authority

基础用法

基础头像由图片与备选回退文本(Fallback)组成,可按需叠加在线状态指示标:

Loading…

安装与引入

通过 CLI 自动添加组件,或手动复制源码至项目中:

pnpm dlx @wui-design/cli@latest add @wui/avatar
安装依赖库
pnpm add radix-ui class-variance-authority clsx tailwind-merge
复制组件源码到 components/ui/avatar.tsx
components/ui/avatar.tsx
"use client"

import * as React from "react"
import { cva } from "class-variance-authority"
import { Avatar as AvatarPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

const avatarVariants = cva(
  "relative inline-flex shrink-0 select-none items-center justify-center overflow-visible rounded-full align-middle",
  {
    variants: {
      size: {
        xs: "size-6 text-[10px]",
        sm: "size-8 text-xs",
        default: "size-10 text-sm",
        lg: "size-12 text-base",
      },
    },
    defaultVariants: {
      size: "default",
    },
  }
)

const avatarBadgeVariants = cva(
  "absolute bottom-0 right-0 z-10 block rounded-full ring-2 ring-background",
  {
    variants: {
      status: {
        online: "bg-success",
        away: "bg-warning",
        busy: "bg-destructive",
        offline: "bg-muted-foreground",
      },
      size: {
        sm: "size-2",
        default: "size-2.5",
      },
    },
    defaultVariants: {
      status: "online",
      size: "default",
    },
  }
)

export interface AvatarProps
  extends React.ComponentProps<typeof AvatarPrimitive.Root> {
  /** Physical avatar size. @default "default" */
  size?: "xs" | "sm" | "default" | "lg"
}

/** A person, team, or agent identity with image fallback support. */
function Avatar({ className, size = "default", ...props }: AvatarProps) {
  return (
    <AvatarPrimitive.Root
      data-slot="avatar"
      data-size={size}
      className={cn(avatarVariants({ size }), className)}
      {...props}
    />
  )
}

function AvatarImage({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
  return (
    <AvatarPrimitive.Image
      data-slot="avatar-image"
      className={cn(
        "size-full rounded-[inherit] object-cover animate-in fade-in-0 duration-300 motion-reduce:animate-none",
        className
      )}
      {...props}
    />
  )
}

function AvatarFallback({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
  return (
    <AvatarPrimitive.Fallback
      data-slot="avatar-fallback"
      className={cn(
        "flex size-full items-center justify-center rounded-[inherit] bg-muted font-medium text-muted-foreground",
        className
      )}
      {...props}
    />
  )
}

export interface AvatarBadgeProps extends React.ComponentProps<"span"> {
  /** Presence meaning represented by the badge color. @default "online" */
  status?: "online" | "away" | "busy" | "offline"
  /** Badge diameter. @default "default" */
  size?: "sm" | "default"
}

const statusLabels: Record<NonNullable<AvatarBadgeProps["status"]>, string> = {
  online: "在线",
  away: "离开",
  busy: "忙碌",
  offline: "离线",
}

function AvatarBadge({
  className,
  status = "online",
  size = "default",
  ...props
}: AvatarBadgeProps) {
  return (
    <span
      data-slot="avatar-badge"
      data-status={status}
      role="img"
      aria-label={statusLabels[status]}
      className={cn(avatarBadgeVariants({ status, size }), className)}
      {...props}
    />
  )
}

export interface AvatarGroupProps extends React.ComponentProps<"div"> {
  /**
   * Spread the stacked avatars apart on hover and lift the hovered one, so
   * every member stays identifiable. Changes the group width while hovered.
   * @default false
   */
  spreadOnHover?: boolean
}

/**
 * Overlaps avatars into a compact stack. Rules target direct children rather
 * than `data-slot=avatar`, so avatars wrapped by `TooltipTrigger asChild` (which
 * overrides the slot) still stack correctly.
 */
function AvatarGroup({
  className,
  spreadOnHover = false,
  ...props
}: AvatarGroupProps) {
  return (
    <div
      data-slot="avatar-group"
      data-spread={spreadOnHover || undefined}
      role="group"
      className={cn(
        "flex items-center [&>*]:ring-2 [&>*]:ring-background [&>*:not(:first-child)]:-ml-2",
        spreadOnHover &&
          "[&>*]:transition-[margin,translate] [&>*]:duration-300 [&>*]:ease-[cubic-bezier(0.22,1,0.36,1)] [&:hover>*:not(:first-child)]:ml-1 [&>*:hover]:z-10 [&>*:hover]:-translate-y-0.5 motion-reduce:[&>*]:transition-none",
        className
      )}
      {...props}
    />
  )
}

export interface AvatarGroupCountProps extends React.ComponentProps<"span"> {
  /** Match the count indicator to the avatars in the group. @default "default" */
  size?: "xs" | "sm" | "default" | "lg"
}

function AvatarGroupCount({
  className,
  size = "default",
  ...props
}: AvatarGroupCountProps) {
  return (
    <span
      data-slot="avatar-group-count"
      data-size={size}
      className={cn(
        avatarVariants({ size }),
        "bg-muted font-medium text-muted-foreground tabular-nums ring-2 ring-background",
        className
      )}
      {...props}
    />
  )
}

export {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarGroupCount,
  AvatarImage,
  avatarBadgeVariants,
  avatarVariants,
}

属性 Props

Avatar

根容器组件,继承 radix-ui Avatar Root 的全部属性:

属性类型默认值说明
size"xs" | "sm" | "default" | "lg""default"头像的尺寸规格。对应直径分别为 24px、32px、40px、48px。
asChildbooleanfalse是否将属性与行为合并到唯一的子元素上渲染。
classNamestring—应用于头像根容器的额外 CSS 类名。
childrenReact.ReactNode—子元素集合,通常包含 AvatarImage、AvatarFallback 和 AvatarBadge。

AvatarImage

负责展示头像图片:

属性类型默认值说明
srcstring—头像图片的资源地址链接。
altstring—图片的替代文本描述,用于屏幕阅读器及无障碍识别。
onLoadingStatusChange(status: "idle" | "loading" | "loaded" | "error") => void—图片加载状态变更时的回调函数。
classNamestring—应用于图片元素的额外 CSS 类名。

AvatarFallback

当图片加载中或加载失败时显示的占位内容:

属性类型默认值说明
delayMsnumber—延迟显示占位内容的毫秒数,避免快速加载时产生闪烁。
childrenReact.ReactNode—占位显示的内容,通常为用户姓名缩写、图标或首字母。
classNamestring—应用于回退容器的额外 CSS 类名。

AvatarBadge

位于头像右下角的状态指示标:

属性类型默认值说明
status"online" | "away" | "busy" | "offline""online"用户在线或当前工作状态的色彩语义。
size"sm" | "default""default"状态指示徽标的物理直径大小。
classNamestring—应用于状态徽标的额外 CSS 类名。

AvatarGroup / AvatarGroupCount

多头像重叠组及剩余数量标签容器:

属性类型默认值说明
spreadOnHoverbooleanfalse仅 AvatarGroup。悬停时头像组平滑展开、当前头像轻微上浮,便于逐一辨认成员;展开期间头像组宽度会变化。
size"xs" | "sm" | "default" | "lg""default"应用于 AvatarGroupCount 的尺寸规格,需与组内头像保持一致。
classNamestring—应用于头像组容器或剩余计数标签的额外 CSS 类名。

事件 Events

属性类型默认值说明
onLoadingStatusChange(status: "idle" | "loading" | "loaded" | "error") => void—当 AvatarImage 图片加载状态变化时触发,返回当前的加载阶段标识。
onClick(event: React.MouseEvent<HTMLSpanElement>) => void—当头像容器被点击时触发(例如点击打开用户资料抽屉或卡片)。

使用场景与设计规范

Avatar 用于代表真实用户、团队实体或系统助理(Bot),在界面中建立清晰的视觉身份感知。

  • 始终提供 Fallback:网络延迟或图片失效在真实业务中极常见。必须在每个 Avatar 内部提供有意义的 AvatarFallback(如姓名简写或通用用户图标),绝不要留空。
  • 状态语义规范:
    • online(绿色):活跃/在线。
    • away(黄色/橙色):离开/挂起。
    • busy(红色):忙碌/请勿打扰/通话中。
    • offline(灰色):离线/未登入。
  • 头像组截断:当协作成员超过 3~5 人时,应当使用 AvatarGroupCount 进行聚合收起(如 +5),避免横向排版挤占过多操作区域。

场景示例

尺寸规格

组件提供 xs (24px)、sm (32px)、default (40px)、lg (48px) 四种尺寸:

Loading…
  • xs (24px):适合密集表格行、行内协作成员微标。
  • sm (32px):适合导航栏右上角个人中心、评论列表子楼层。
  • default (40px):通用标准尺寸,适用于列表项、卡片头部与聊天界面。
  • lg (48px):适合个人中心资料页大头像、详细信息展示卡片。

在线状态指示

通过 AvatarBadge 搭配不同状态,展示即时通讯与协作状态:

Loading…

回退策略与自定义占位

图片加载完成后 AvatarImage 会淡入呈现;当头像图片因网络错误无法加载时,AvatarFallback 会自动展现。支持文字缩写、品牌底色、系统图标等多种形式:

Loading…

头像重叠组 (AvatarGroup)

在任务看板、项目协作或权限管理中,使用 AvatarGroup 紧凑展示团队参与者。开启 spreadOnHover 后,悬停时头像平滑展开,可配合 Tooltip 展示成员姓名:

Loading…

业务场景:协作者管理面板

在项目设置或工作区成员列表中,结合头像、状态徽章、身份角色标签与操作按钮构建完整的成员信息卡:

Loading…

无障碍与交互 Accessibility

  • 替代文本 (Alt Text):始终在 AvatarImage 上传入准确描述用户名称的 alt 属性(如 alt="张三"),读屏器会自动朗读。
  • 状态播报:AvatarBadge 默认渲染 role="img" 与中文 aria-label(在线 / 离开 / 忙碌 / 离线),确保视障用户也能感知当前实体的在线状态;可通过传入 aria-label 覆盖。
  • 动效降级:图片淡入与头像组展开动效在系统开启“减少动态效果”时自动关闭。
  • 分组语义:AvatarGroup 自带 role="group",方便屏幕阅读器将一系列关联头像理解为一个整体团队单元。
  • 平滑过渡:内置图片加载与回退逻辑支持 delayMs 防抖,避免在快速网络环境下出现 Fallback 瞬时闪烁现象。