Avatar component
Avatar renders a user's profile image with a graceful fallback to initials (derived from name) or an icon when there's no src, or while the image is still loading. A companion AvatarGroup component stacks several Avatars with automatic overflow counting, for things like a list of collaborators on a document.
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Avatar actually gets used — each one a real, self-contained example.
Image avatar
Pass src for a photo; name gives it an accessible label and an initials fallback if the image fails to load.
<Avatar src="/hero/person-woman.jpg" name="Sara Whitfield" />
<Avatar src="/hero/person-man.jpg" name="Marcus Lee" />Initials fallback
With no src, Avatar renders the name's initials — colour it with the color prop for variety across a list.
<Avatar name="Sara Whitfield" color="primary" />
<Avatar name="Marcus Lee" color="success" />
<Avatar name="Ada Lovelace" color="warning" />Sizes
Six size tokens (or a raw pixel number) — small for dense lists, large for profile headers.
<Avatar name="AB" size="xs" />
<Avatar name="AB" size="sm" />
<Avatar name="AB" size="md" />
<Avatar name="AB" size="lg" />
<Avatar name="AB" size="xl" />Presence status
status adds an online/offline/dnd indicator dot — presence without a separate element.
<Avatar src="/hero/person-woman.jpg" name="Sara" status="online" />
<Avatar name="Marcus Lee" status="dnd" />
<Avatar name="Ada Lovelace" status="offline" />Avatar group
AvatarGroup overlaps avatars and collapses the overflow into a “+N” once past max — the standard “who's on this” cluster.
<AvatarGroup max={3}>
<Avatar src="/hero/person-woman.jpg" name="Sara" />
<Avatar src="/hero/person-man.jpg" name="Marcus" />
<Avatar name="Ada Lovelace" />
<Avatar name="Grace Hopper" />
<Avatar name="Alan Turing" />
</AvatarGroup>Group with name tooltips
Avatar's own tooltip prop wraps it in a Tooltip -- pass true (or just the prop with no value) to derive the label from name, or a string/object for full control. AvatarGroup clones its size/color/radius/... group defaults onto each Avatar before that wrapping happens, so tooltip composes with it with no extra wiring.
<AvatarGroup max={4}>
<Avatar tooltip src="/hero/person-woman.jpg" name="Sara Whitfield" />
<Avatar tooltip src="/hero/person-man.jpg" name="Marcus Lee" />
<Avatar tooltip name="Ada Lovelace" />
<Avatar tooltip name="Grace Hopper" />
<Avatar tooltip name="Alan Turing" />
</AvatarGroup>Image with automatic fallback
Pass src for the image and name so there's something sensible to fall back to — Avatar derives initials from name automatically if the image fails to load or hasn't loaded yet. If you don't have a name either, pass icon for a generic fallback instead of leaving it blank.
<Avatar src={user.avatarUrl} name={user.fullName} />
<Avatar name="Ada Lovelace" /> {/* no src -- renders "AL" */}
<Avatar icon={<UserIcon />} /> {/* no src, no name -- renders the icon */}Status indicator
status renders a small colored dot in the corner for a presence indicator (online, offline, dnd) — built on the same positioning machinery Badge uses for its own dot mode, so it stays correctly anchored across every avatar size.
<Avatar src={user.avatarUrl} name={user.fullName} status="online" />Fully custom fallback
For anything beyond initials or a single icon — a placeholder illustration, a different fallback per user type — pass fallback with any ReactNode; it takes priority over both icon and the derived initials. showFallback forces the fallback to render even while a valid image is still loading, useful if you'd rather show a stable placeholder than a flash of empty space.
Rendering through a different image component
By default the image renders as a plain <img>. Pass ImgComponent to render it through something else instead — next/image, for instance — with imgProps carrying whatever extra props that component needs (width/height, loader, etc.).
<Avatar
src={user.avatarUrl}
name={user.fullName}
ImgComponent={Image}
imgProps={{ width: 40, height: 40 }}
/>Grouping avatars with AvatarGroup
AvatarGroup stacks its children with a configurable overlap and shows a "+N" overflow count once there are more avatars than max — pass total instead of relying on children.length if the real total (e.g. from a paginated API) is larger than what's actually rendered.
import { Avatar, AvatarGroup } from "oks-ui";
<AvatarGroup max={4} total={12}>
{collaborators.map((c) => (
<Avatar key={c.id} src={c.avatarUrl} name={c.name} />
))}
</AvatarGroup>Props
The full public API for Avatar. Every prop is stable and intentionally typed.
Content
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | — | Used to derive fallback initials. |
| src | string | — | Image source URL. |
| icon | ReactNode | — | Custom fallback icon, used when name/initials aren't available. |
| fallback | ReactNode | — | Fully custom fallback content, overrides icon/initials. |
| showFallback | boolean | false | Forces the fallback (initials/icon) even while an image is loading. |
| AvatarGroup | { max?, total?, size?, color?, radius?, isGrid?, isDisabled?, isBordered?, overlap?, renderCount?, classNames? } | — | Stacks Avatar children and collapses whatever's past max (default 5) into a "+N" -- pass total instead of relying on children.length if you're not rendering every avatar. size/color/radius/isBordered/isDisabled cascade onto every child that doesn't set its own (a child's tooltip prop is untouched, so per-avatar tooltips still work). overlap is the px overlap between avatars (default 14); isGrid wraps instead of stacking; renderCount(count) customizes the overflow label. |
name
string
default: —
Used to derive fallback initials.
src
string
default: —
Image source URL.
icon
ReactNode
default: —
Custom fallback icon, used when name/initials aren't available.
fallback
ReactNode
default: —
Fully custom fallback content, overrides icon/initials.
showFallback
boolean
default: false
Forces the fallback (initials/icon) even while an image is loading.
AvatarGroup
{ max?, total?, size?, color?, radius?, isGrid?, isDisabled?, isBordered?, overlap?, renderCount?, classNames? }
default: —
Stacks Avatar children and collapses whatever's past max (default 5) into a "+N" -- pass total instead of relying on children.length if you're not rendering every avatar. size/color/radius/isBordered/isDisabled cascade onto every child that doesn't set its own (a child's tooltip prop is untouched, so per-avatar tooltips still work). overlap is the px overlap between avatars (default 14); isGrid wraps instead of stacking; renderCount(count) customizes the overflow label.
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | number | md | Token size, or a number for an exact px size (font-size scales ~0.4x; the status Badge maps to the nearest token bucket). AvatarGroup forces its size onto children. |
| color | "default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | default | Semantic color for the fallback background. |
| radius | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | full | Border radius. |
| colorDepth | 50 | 100 | ... | 950 | 500 | Shade depth for the selected color. |
| isBordered | boolean | false | Adds a ring border around the avatar. |
size
"xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | number
default: md
Token size, or a number for an exact px size (font-size scales ~0.4x; the status Badge maps to the nearest token bucket). AvatarGroup forces its size onto children.
color
"default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger"
default: default
Semantic color for the fallback background.
radius
"none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full"
default: full
Border radius.
colorDepth
50 | 100 | ... | 950
default: 500
Shade depth for the selected color.
isBordered
boolean
default: false
Adds a ring border around the avatar.
Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isDisabled | boolean | false | Dims the avatar and disables interaction. |
| isFocusable | boolean | false | Makes the avatar keyboard-focusable. |
| tooltip | ReactNode | boolean | (Omit<TooltipProps, "children"|"content"> & { content?: ReactNode }) | false | Wraps the avatar in a Tooltip. A plain value is shorthand content; bare true derives content from name; an object accepts any Tooltip prop plus an optional content override. |
| status | "online" | "offline" | "dnd" | — | Status dot indicator. |
| ImgComponent | ElementType | "img" | Renders the image through a different component (e.g. next/image). |
| imgProps | ComponentPropsWithoutRef<ImgComponent> | — | Extra props forwarded to the image element/component. |
isDisabled
boolean
default: false
Dims the avatar and disables interaction.
isFocusable
boolean
default: false
Makes the avatar keyboard-focusable.
tooltip
ReactNode | boolean | (Omit<TooltipProps, "children"|"content"> & { content?: ReactNode })
default: false
Wraps the avatar in a Tooltip. A plain value is shorthand content; bare true derives content from name; an object accepts any Tooltip prop plus an optional content override.
status
"online" | "offline" | "dnd"
default: —
Status dot indicator.
ImgComponent
ElementType
default: "img"
Renders the image through a different component (e.g. next/image).
imgProps
ComponentPropsWithoutRef<ImgComponent>
default: —
Extra props forwarded to the image element/component.
Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| classNames | Partial<Record<AvatarSlot, string>> | — | Per-slot class overrides (base/img/fallback/name/icon/status). |
| className | string | — | Class applied to the root element. |
classNames
Partial<Record<AvatarSlot, string>>
default: —
Per-slot class overrides (base/img/fallback/name/icon/status).
className
string
default: —
Class applied to the root element.
Accessibility
- name doubles as the source for derived initials and should be the person's real display name — it's what a screen reader falls back to describing when there's no explicit label.
- isFocusable makes an otherwise purely decorative avatar keyboard-reachable, for cases where the avatar itself is interactive (opens a profile card, a menu) rather than just illustrative.
- status is a purely visual dot by default — if the presence state needs to be announced, pair it with your own aria-label or adjacent text, since color alone doesn't reach assistive technology.
Best practices
- Always pass name even when src is set — it's the fallback the user actually sees if the image 404s, and costs nothing when the image loads fine.
- Use showFallback when a slow image load would otherwise cause a visible flash from blank to loaded; it keeps the layout looking intentional the whole time.
- In AvatarGroup, set max low enough that the stack stays readable — beyond 4-5 overlapping avatars the individual faces become hard to distinguish anyway, and the overflow count communicates the rest just fine.
- Reach for ImgComponent + imgProps when your app already has an optimized image component (next/image, a CDN-aware wrapper) — piping avatars through it keeps their loading behavior consistent with the rest of the app's images.