Avatar

Represent a person, a team or a thing with an image, initials or an icon, with an indicator on a corner or an edge, a ring and group stacking that hold at every size.

Code

antuikit's Avatar is Ant Design's Avatar with a ring, a readable default color, a squircle value on shape, a variant and color pair, and an indicator slot on the shape's corner. Every other prop behaves as in Ant Design.

Install

npx antuikit@latest init
npx antuikit@latest add avatar
Avatar installs AntuikitProvider along with it. The provider styles a group's +n chip to match the avatars.

Import

Avatar.Group is not re-exposed by the kit. It is a separate antd component with its own max and overlap API, so it comes from antd directly, and its children are still the kit's Avatar.
import { Avatar } from "@/components/ui/avatar"
import { Avatar as AntdAvatar } from "antd" // Avatar.Group lives here
<Avatar src="/avatars/ada.png" alt="Ada Lovelace" />

Content

Three routes, in order: a src wins, then icon, then children. There is no default icon, so an avatar with none of the three renders empty.
1
import { Avatar } from "@/components/ui/avatar"
2
import { Icon } from "@/components/ui/icon"
3
4
<Avatar src="/avatars/ada.png" alt="Ada Lovelace" />
5
<Avatar icon={<Icon name="file" />} />
6
<Avatar>AL</Avatar>
Text in a letter avatar scales down until it fits rather than being clipped. gap is the unit distance it leaves on each side, so a larger gap means a smaller font.
<Avatar>A</Avatar>
<Avatar size={48}>USER</Avatar>
<Avatar size={48} gap={2}>LONGER</Avatar>

variant + color

The kit adds both props. They use the same three values as Tag, Button and Alert.
  • filled: a light tint under dark ink from the same ramp. The readable one, and the default when you set color alone.
  • solid: the ramp's mid tone under a light label.
  • outlined: no fill, a ramp border and dark ink.
1
{/* color alone means filled */}
2
<Avatar color="geekblue">GE</Avatar>
3
4
<Avatar color="purple" variant="filled">PU</Avatar>
5
<Avatar color="purple" variant="solid">PU</Avatar>
6
<Avatar color="purple" variant="outlined">PU</Avatar>
color takes antd's presets (blue, green, gold and the rest) or a palette from your theme (the default theme adds amber, emerald, teal, sky, indigo, violet, fuchsia, rose and grays like slate and zinc). A name with no ramp, like a hex, tomato or a status name, leaves the avatar unpainted.
<Avatar color="fuchsia" variant="filled">FU</Avatar>
{/* No ramp, so all three stay unpainted */}
<Avatar color="tomato">TM</Avatar>
<Avatar color="success">SC</Avatar>
<Avatar color="#87d068">IN</Avatar>
{/* For an arbitrary color, inline style beats every utility */}
<Avatar style={{ backgroundColor: "#87d068" }}>IN</Avatar>

Shape

  • circle: the default
  • square: rounded at borderRadius
  • squircle: a smooth superellipse corner, added by the kit
<Avatar shape="circle" src="/avatars/ada.png" alt="" />
<Avatar shape="square" src="/avatars/ada.png" alt="" />
<Avatar shape="squircle" src="/avatars/ada.png" alt="" />
A squircle renders as square in browsers without corner-shape support, so never let the shape be the only difference between two meanings.

Size

A number in pixels, one of three named steps, or an object of breakpoints.
  • large: 40px
  • medium: 32px (default). middle is v5's name for the same step.
  • small: 24px
<Avatar size="large" src="/avatars/ada.png" alt="" />
<Avatar size="medium" src="/avatars/ada.png" alt="" />
<Avatar size="small" src="/avatars/ada.png" alt="" />
<Avatar size={56} src="/avatars/ada.png" alt="" />
Responsive size
The breakpoints are the same ones the kit's Tailwind sm: to 2xl: classes use, but the largest key is spelled xxl, where a class says 2xl.
<Avatar
size={{ xs: 24, sm: 32, md: 40, lg: 48, xl: 56, xxl: 64 }}
src="/avatars/ada.png"
alt=""
/>

The corner: indicator

indicator puts a node on the edge of the shape, on a corner or the middle of the top or bottom edge, at any size and on any shape, with nothing to tune. Use it instead of wrapping the avatar in a <Badge>, which puts the node on the box corner, off the circle, and needs an offset per avatar size.
1
import { Badge } from "antd"
2
import { Avatar } from "@/components/ui/avatar"
3
4
{/* A presence dot: pass a childless Badge */}
5
<Avatar
6
src="/avatars/ada.png"
7
alt="Ada Lovelace"
8
indicator={<Badge status="success" dot />}
9
indicatorPosition="bottom-end"
10
/>
11
12
{/* A count */}
13
<Avatar src="/avatars/ada.png" alt="" indicator={<Badge count={3} />} />
14
15
{/* Anything else: a flag, a brand glyph, a camera button */}
16
<Avatar
17
size={64}
18
src="/avatars/ada.png"
19
alt=""
20
indicatorPosition="bottom-end"
21
indicator={
22
<Button shape="circle" size="small" icon={<Icon name="edit" />} aria-label="Change photo" />
23
}
24
/>
indicatorPosition takes the four corners, top-start, top-end (the default), bottom-start and bottom-end, plus top-center and bottom-center for the middle of an edge. start and end follow the text direction, so they swap under dir="rtl".

The ring

Every avatar has a thin border ring. It is transparent on a lone avatar and colored inside a group, where it separates the faces. Give it a border-* class to change its color.
1
{/* Taking the ring over */}
2
<Avatar src="/avatars/ada.png" alt="" className="border-primary" />
3
4
{/* An offset ring sits outside the avatar, for a selected state */}
5
<Avatar
6
src="/avatars/ada.png"
7
alt=""
8
className="ring-2 ring-primary ring-offset-2 ring-offset-container"
9
/>

States

When the image fails
icon and children are the fallback for a failed image, in that priority. Return false from onError to keep the broken image, for when you retry the URL yourself.
<Avatar src="/missing.png" icon={<Icon name="file" />} />
<Avatar src="/missing.png">AL</Avatar>
Loading
Avatar has no loading state of its own. Use Skeleton.Avatar, which draws the same shape at the same size, so the row does not jump when the data lands. It takes the same size and shape values.
import { Skeleton } from "antd"
{loading
? <Skeleton.Avatar active size="large" shape="circle" />
: <Avatar size="large" src={user.photo} alt={user.name} />}
Decoration
There is no filter prop. Put a decorative treatment in className.
<Avatar src="/avatars/ada.png" alt="" className="grayscale" />
<Avatar src="/avatars/ada.png" alt="" className="grayscale hover:grayscale-0 motion-safe:transition" />

Making an avatar clickable

AvatarProps is a closed list and does not extend HTMLAttributes, so there is no role, tabIndex or aria-label to put beside an onClick. A bare clickable avatar is unreachable by keyboard and unnamed to a screen reader. Put it inside a Button.
1
import { Button } from "@/components/ui/button"
2
3
{/* Button's own avatar prop: the picture, plus the semantics for free */}
4
<Button
5
shape="circle"
6
avatar="/avatars/ada.png"
7
aria-label="Open your account menu"
8
/>
9
10
{/* Or wrap, when the trigger is more than the face */}
11
<Button variant="text" color="default" onClick={openProfile}>
12
<Avatar size="small" src="/avatars/ada.png" alt="" />
13
Ada Lovelace
14
</Button>

Avatar.Group

Imported from antd directly. It stacks, spaces and overflows a set of avatars. size and shape set on the group apply to every avatar inside it.
1
import { Avatar as AntdAvatar } from "antd"
2
import { Avatar } from "@/components/ui/avatar"
3
4
<AntdAvatar.Group size="large" max={{ count: 3 }}>
5
<Avatar src="/avatars/1.png" alt="" />
6
<Avatar src="/avatars/2.png" alt="" />
7
<Avatar src="/avatars/3.png" alt="" />
8
<Avatar src="/avatars/4.png" alt="" />
9
<Avatar src="/avatars/5.png" alt="" />
10
</AntdAvatar.Group>
The overflow chip
max is an object: count, style and a popover config. The flat maxCount, maxStyle and the two maxPopover* props are deprecated and each logs a warning.
<AntdAvatar.Group
max={{
count: 3,
popover: { trigger: "hover", placement: "top" },
}}
>
{members.map((m) => (
<Tooltip key={m.id} title={m.name}>
<Avatar src={m.photo} alt="" />
</Tooltip>
))}
</AntdAvatar.Group>
How much they overlap
Setting --ant-avatar-group-overlapping on the group does nothing. Put a margin class on the children instead, or set the component token.
{/* Works: the margin on the children */}
<AntdAvatar.Group className="[&_.ant-avatar]:-ms-3">
<Avatar src="/avatars/1.png" alt="" />
<Avatar src="/avatars/2.png" alt="" />
</AntdAvatar.Group>
// Or for the whole app, in tokens/antuikit.tokens.json
"components": { "Avatar": { "groupOverlapping": -12 } }
A stacked group hides the names, so the usual fix is a Tooltip per avatar. The Tooltip wraps the Avatar rather than the other way round: it needs a child it can attach a trigger to, and AvatarProps carries no title.

API Reference

Avatar
Avatar's props. Rows marked antuikit in the Version column are added by the kit.
Avatar.Group
Avatar.Group's props.