Avatar
A person or a project as a picture, or as initials when there is no picture; overlapping in a group with a counter.
- CUcennge-uiReact UI library
- ASai-niche-scoutFinds niches worth building for
- PCpayload-cacheFastAPI cache service
- SSspredo-crypto-screenerCrypto screener
- PSplaywright-msi-scraperScraper on Playwright
- With src, the picture shows once it has loaded. If loading takes over 300 ms, the initials show meanwhile; if it fails, they stay.
- Initials are the first letters of the first and last word of
name; dashes, dots and underscores split words too, so ai-niche-scout is AS. AvatarGroupshows the firstmaxavatars and a +N counter for the rest.
Field marks
- Import
import { Avatar, AvatarGroup } from "cennge-ui";- Size
- 1.1 KB gzip for this import, its dependencies aside
- Pattern
- Radix Avatar: an
<img>once it has loaded, otherwise the fallback - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Alt text
- From
alt, otherwisename - Fallback
- Initials from
name, or your own node; after 300 ms when there is a picture - Tests
- Unit tests
- Source
- src/components/avatar
When to use#
An Avatar shows who or what something belongs to: the account in a header, the author of a comment, the owner of a repository. The name does two jobs: it is the picture's alt text, and without a picture it becomes the initials, set on one of seven color tints.
Similar#
- Indicator puts a status dot or a count on the avatar's corner; the two go together.
- Skeleton holds the avatar's place while the account itself is still loading. An Avatar waiting for its picture already shows initials.
Picture, initials or your own#
A single word gives its first two letters; several give the first letter of the first and last. fallback replaces the initials with anything, such as an icon for a signed-out visitor.
import { Avatar } from "cennge-ui";
function PersonIcon() {
return (
<svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<circle cx="12" cy="8.5" r="3.75" />
<path d="M4.75 20c.9-3.6 3.75-5.75 7.25-5.75s6.35 2.15 7.25 5.75" strokeLinecap="round" />
</svg>
);
}
export default function AvatarFallback() {
return (
<div className="flex flex-wrap items-center gap-4">
{/* The picture, once it loads; alt text from name. */}
<Avatar src="/avatar.svg" name="Cennge" size="lg" />
{/* No picture: the first two letters of a single word. */}
<Avatar name="Cennge" size="lg" />
{/* First and last word: payload-cache is PC. */}
<Avatar name="payload-cache" color="success" size="lg" />
{/* Your own fallback instead of initials, e.g. for a signed-out visitor. */}
<Avatar fallback={<PersonIcon />} size="lg" />
</div>
);
}Sizes and shapes#
Four sizes, from 32 to 64 pixels. Avatars are round by default; radius="md" reads as a project or an organisation rather than a person.
import { Avatar, type AvatarSize } from "cennge-ui";
const sizes: AvatarSize[] = ["sm", "md", "lg", "xl"];
export default function AvatarSizes() {
return (
<div className="flex flex-col gap-5">
<div className="flex flex-wrap items-center gap-4">
{sizes.map((size) => (
<Avatar key={size} src="/avatar.svg" name="Cennge" size={size} />
))}
</div>
{/* Round for people, a softer square for projects. */}
<div className="flex flex-wrap items-center gap-4">
{sizes.map((size) => (
<Avatar key={size} name="ai-niche-scout" color="info" radius="md" size={size} />
))}
</div>
</div>
);
}A group with a counter#
AvatarGroup overlaps its avatars and, past max, adds a counter of the rest. isBordered draws a ring in the page colour around each avatar so the overlaps stay apart. The group's size sets only the counter's size, so give it the same size as the avatars.
import { Avatar, AvatarGroup, type CenngeColor } from "cennge-ui";
const repositories: { name: string; color: CenngeColor }[] = [
{ name: "cennge-ui", color: "primary" },
{ name: "ai-niche-scout", color: "info" },
{ name: "payload-cache", color: "success" },
{ name: "spredo-crypto-screener", color: "warning" },
{ name: "playwright-msi-scraper", color: "secondary" },
];
export default function RepositoryStack() {
return (
<div className="flex flex-col gap-5">
<AvatarGroup max={3}>
{repositories.map((repository) => (
<Avatar key={repository.name} name={repository.name} color={repository.color} isBordered />
))}
</AvatarGroup>
{/* The group's size is the counter's: give it the avatars' size. */}
<AvatarGroup max={4} size="lg">
{repositories.map((repository) => (
<Avatar key={repository.name} name={repository.name} color={repository.color} size="lg" isBordered />
))}
</AvatarGroup>
</div>
);
}With a status#
Wrap an avatar in an Indicator with shape="circle": the dot moves in from the empty corner onto the edge of the circle. Its label is what screen readers hear.
import { Avatar, Indicator } from "cennge-ui";
export default function AvatarStatus() {
return (
<div className="flex flex-wrap items-center gap-6">
<Indicator shape="circle" color="success" label="Online">
<Avatar src="/avatar.svg" name="Cennge" size="lg" />
</Indicator>
<Indicator shape="circle" content={3} label="3 failed CI runs">
<Avatar name="payload-cache" color="success" size="lg" />
</Indicator>
</div>
);
}Server rendering#
An avatar without src is rendered with its initials on the server. One with src is rendered empty: the picture is shown only after the browser has loaded it, and the initials only if it has not loaded within 300 ms, so a cached picture does not flash letters first.
Props#
Avatar#
Also takes every attribute of <span>, except color, which mean something else here.
AvatarGroup#
Also takes every attribute of <div>.