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.
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
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>
icon takes any node, so pass an Icon by name and it follows whichever icon pack your project installs.
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.
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.
solid puts white on the ramp's mid tone, which misses WCAG AA on ten of the twelve presets, with gold and yellow the worst. Use filled on a warm hue, or solid on a neutral ramp, whose fill is darker.
Image avatars ignore variant and color. The picture is the content, and the color comes back if the src fails.
Shape
circle: the default
square: rounded at borderRadius
squircle: a smooth superellipse corner, added by the kit
A named size keeps the theme's text size at every step, so only the box changes. A numeric size keeps an 18px font whatever the box, so set style={{ fontSize }} to change it. A size-* class skips the text scaling, so initials may not fit.
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.
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.
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 slot adds one wrapper element, and only when set. className, style and ref all still go to the avatar itself. What decides whether the node is a control or a state is the node: a Button brings the role, the focus ring and Enter/Space with it, a plain span does not and should not.
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.
The border ring sits inside the avatar's box, so it covers the edge of the picture and adds nothing to the outer size. A ring-* is drawn outside it, and ring-offset-container sets the color of the gap.
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 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" />
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 */}
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>
AntuikitProvider gives the +n chip the same fill, border and text color as the avatars, so a group reads as one set. To recolor it, pass max={{ style }}.
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.
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.