组件
头像 Avatar
用于展示用户、团队或机器人身份的图形元素,内置图片加载回退、在线状态徽章与多头像重叠组能力。
基础用法
基础头像由图片与备选回退文本(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"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。 |
| asChild | boolean | false | 是否将属性与行为合并到唯一的子元素上渲染。 |
| className | string | — | 应用于头像根容器的额外 CSS 类名。 |
| children | React.ReactNode | — | 子元素集合,通常包含 AvatarImage、AvatarFallback 和 AvatarBadge。 |
AvatarImage
负责展示头像图片:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| src | string | — | 头像图片的资源地址链接。 |
| alt | string | — | 图片的替代文本描述,用于屏幕阅读器及无障碍识别。 |
| onLoadingStatusChange | (status: "idle" | "loading" | "loaded" | "error") => void | — | 图片加载状态变更时的回调函数。 |
| className | string | — | 应用于图片元素的额外 CSS 类名。 |
AvatarFallback
当图片加载中或加载失败时显示的占位内容:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| delayMs | number | — | 延迟显示占位内容的毫秒数,避免快速加载时产生闪烁。 |
| children | React.ReactNode | — | 占位显示的内容,通常为用户姓名缩写、图标或首字母。 |
| className | string | — | 应用于回退容器的额外 CSS 类名。 |
AvatarBadge
位于头像右下角的状态指示标:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| status | "online" | "away" | "busy" | "offline" | "online" | 用户在线或当前工作状态的色彩语义。 |
| size | "sm" | "default" | "default" | 状态指示徽标的物理直径大小。 |
| className | string | — | 应用于状态徽标的额外 CSS 类名。 |
AvatarGroup / AvatarGroupCount
多头像重叠组及剩余数量标签容器:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| spreadOnHover | boolean | false | 仅 AvatarGroup。悬停时头像组平滑展开、当前头像轻微上浮,便于逐一辨认成员;展开期间头像组宽度会变化。 |
| size | "xs" | "sm" | "default" | "lg" | "default" | 应用于 AvatarGroupCount 的尺寸规格,需与组内头像保持一致。 |
| className | string | — | 应用于头像组容器或剩余计数标签的额外 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 瞬时闪烁现象。