Content
Avatar
Show a person's photo, with their initials until it loads or if it fails.
import { Avatar } from "macbetter/avatar"
export default function Example() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
<Avatar size="xl" name="Maya Okafor" src="/demos/avatar-maya.svg" />
<Avatar size="lg" name="Theo Brandt" src="/demos/avatar-theo.svg" />
<Avatar size="md" name="Priya Raman" />
<Avatar size="sm" name="Jonas Weber" />
<Avatar size="xs" />
</div>
)
}When to use it
Use an avatar to identify a person in a comment, a member list, a message thread or an account menu. It's an image of who they are, so pair it with their name wherever there's room; on its own it only works for people the viewer already knows.
Use the app shape for things that aren't people: an app, a workspace, a team. The rounded square tells the viewer at a glance that it isn't a face.
Keep sizes consistent within a list. iOS and macOS use one size per context, such as 40 points in a contacts list and 24 points in a sidebar.
Usage
import { Avatar, AvatarGroup } from "macbetter/avatar"Pass name and src. The name becomes the image's alt text and the fallback's initials, and a screen reader reads it in either state.
Fallback colors
The fallback is a soft gradient of one system color. gray is the Contacts monogram and the default. auto picks a color from the name, so the same person always gets the same color.
import { Avatar } from "macbetter/avatar"
const people = ["Ines Laurent", "Kenji Sato", "Lena Fischer", "Omar Haddad", "Sofia Rossi", "Wen Li"]
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16, alignItems: "center" }}>
<div style={{ display: "flex", gap: 12 }}>
{people.map((name) => (
<Avatar key={name} size="lg" name={name} />
))}
</div>
<div style={{ display: "flex", gap: 12 }}>
{people.map((name) => (
<Avatar key={name} size="lg" name={name} color="auto" />
))}
</div>
</div>
)
}Status
status adds a dot on the bottom trailing edge, ringed in the surface color. It's read after the name: "Online", "Away", "Do not disturb" or "Offline". Use statusLabel to say something else.
import { Avatar } from "macbetter/avatar"
export default function Example() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 20 }}>
<Avatar size="lg" name="Maya Okafor" src="/demos/avatar-maya.svg" status="online" />
<Avatar size="lg" name="Theo Brandt" src="/demos/avatar-theo.svg" status="away" />
<Avatar size="lg" name="Priya Raman" color="auto" status="busy" />
<Avatar size="lg" name="Jonas Weber" status="offline" />
</div>
)
}Groups
AvatarGroup overlaps avatars, each cut out of the one behind it. Set size on the group and leave it off the avatars. With max, the rest collapse into a count. Give the group an aria-label that says who they are.
import { Avatar, AvatarGroup } from "macbetter/avatar"
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 20 }}>
<AvatarGroup aria-label="Editing this document" size="md">
<Avatar name="Maya Okafor" src="/demos/avatar-maya.svg" />
<Avatar name="Theo Brandt" src="/demos/avatar-theo.svg" />
<Avatar name="Sofia Rossi" src="/demos/avatar-sofia.svg" />
<Avatar name="Priya Raman" color="auto" />
</AvatarGroup>
<AvatarGroup aria-label="Members of Design Review" size="sm" max={5}>
<Avatar name="Maya Okafor" src="/demos/avatar-maya.svg" />
<Avatar name="Theo Brandt" src="/demos/avatar-theo.svg" />
<Avatar name="Sofia Rossi" src="/demos/avatar-sofia.svg" />
<Avatar name="Kenji Sato" src="/demos/avatar-kenji.svg" />
<Avatar name="Lena Fischer" />
<Avatar name="Omar Haddad" />
<Avatar name="Wen Li" />
<Avatar name="Jonas Weber" />
</AvatarGroup>
</div>
)
}App icons
shape="app" is the rounded square of an app icon. Browsers that support corner-shape draw Apple's continuous corner; others draw a plain 22.37% radius.
import { Avatar, AvatarFallback, AvatarImage } from "macbetter/avatar"
export default function Example() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 20 }}>
<Avatar shape="app" size="xl">
<AvatarImage src="/app-icon-square.svg" alt="macbetter" />
<AvatarFallback>D</AvatarFallback>
</Avatar>
<Avatar shape="app" size="xl" name="Marketing" color="orange" />
<Avatar shape="app" size="lg" name="Platform Team" color="indigo" />
<Avatar shape="app" size="md" name="Support" color="green" />
<Avatar shape="app" size="sm" name="Finance" color="teal" />
</div>
)
}As a button
When pressing an avatar opens a profile or an account menu, render it as a button and name the button.
"use client"
import { Avatar } from "macbetter/avatar"
export default function Example() {
return (
<Avatar
size="md"
src="/demos/avatar-maya.svg"
render={<button type="button" aria-label="Account: Maya Okafor" />}
/>
)
}Platform behavior
Avatars look the same on every platform: the sizes are absolute, because a photo's size follows the layout around it rather than the platform's text size. Pick a larger size on iOS and iPadOS rows, which are taller. A pressed avatar button dims, as image buttons do on every Apple platform.
Accessibility
- The image's alt text and the fallback's hidden text both come from
name, so the person is named while the image loads and after it fails. The initials themselves are hidden from screen readers. - When the name is already next to the avatar, leave
nameoff and pass your ownAvatarImagewith an emptyaltso it isn't read twice. - Status is never color alone: it's read as text.
- Rendered as a button or link, an avatar shows the accent focus ring when you reach it with the keyboard. Name the button for what it does, like "Account: Maya Okafor".
- A group is a
groupnamed by itsaria-label, and its overflow count reads as "and 3 more".
API
Avatar
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | The person or thing pictured. Gives the image its alt text and the fallback its initials. |
src | string | — | The photo. While it loads, and if it fails, the fallback shows. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | 20, 28, 36, 52 or 80 pixels. Inside a group, leave it out to take the group's size. |
shape | "circle" | "app" | "circle" | `app` is the rounded square of an app icon. |
color | SystemColor | "gray" | "auto" | "gray" | The fallback gradient's hue. `auto` picks one from the name. |
status | "online" | "away" | "busy" | "offline" | — | Adds a status dot. |
statusLabel | string | — | Read in place of the default word for the status. |
children | ReactNode | — | Your own AvatarImage and AvatarFallback, in place of the ones made from src and name. |
Avatar also takes every prop of ui-sdk's Avatar, including render and onLoadingStatusChange. AvatarImage and AvatarFallback take ui-sdk's props.
AvatarGroup
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | The size of every avatar in the group that doesn't set its own. |
max | number | — | How many to show, counting the overflow count. |
Data attributes and variables
| Attribute or variable | On | Description |
|---|---|---|
data-loading-status | .mb-avatar | idle, loading, loaded or error, from ui-sdk. |
data-size, data-shape, data-color | .mb-avatar | The props, for styling. |
data-status | .mb-avatar-status | The status. |
--mb-avatar-size | .mb-avatar, .mb-avatar-group | Set any size, not just the five named ones. |
--mb-avatar-ring | any ancestor | The color of the cut-out ring in groups and around the status dot. Defaults to --mb-bg. |