Avatar
Avatar is a neobrutalist React component from Cruise UI. A photo from any URL, falling back to initials or an icon; circle or square, 32–80px, status dots, auto colours and groups. Install it with npx shadcn@latest add @cruiseui/avatar.
Install
Terminal
npx shadcn@latest add @cruiseui/avatarFirst time? Installation shows the one-off components.json setup (the @cruiseui registry) and how to turn the theme on.
Example
Corners
Image from any URL
IBNE
Initials, icon and a broken URL
IBACUMG
Status, sizes and square
TB, onlineKA, awayFO, busyZM, offlineDL
AOTBNEMGKA+2
import type { ReactNode } from "react";
import { CruiseAvatar, CruiseAvatarGroup } from "@/components/cruise/avatar";
const team = [
{ name: "Ada Okafor", src: "https://i.pravatar.cc/120?img=32" },
{ name: "Tunde Bello" },
{ name: "Ngozi Eze", src: "https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_160,w_160/woman.jpg" },
{ name: "Musa Garba" },
{ name: "Kemi Adeyemi" },
{ name: "Femi Ojo" },
{ name: "Zainab Musa" },
];
function Row({ title, children }: { title: string; children: ReactNode }) {
return (
<div className="flex flex-col gap-3">
<p className="text-label uppercase text-ink-muted">{title}</p>
<div className="flex flex-wrap items-center gap-4">{children}</div>
</div>
);
}
export default function AvatarExample() {
return (
<div className="flex flex-col gap-6">
<Row title="Image from any URL">
<CruiseAvatar src="https://i.pravatar.cc/120?img=12" alt="Idris Bankole" name="Idris Bankole" size="lg" />
<CruiseAvatar
src="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_160,w_160/woman.jpg"
alt="Ngozi Eze"
name="Ngozi Eze"
size="lg"
/>
</Row>
<Row title="Initials, icon and a broken URL">
<CruiseAvatar name="Idris Bankole" tone="auto" />
<CruiseAvatar name="Ada" tone="auto" />
<CruiseAvatar initials="CU" name="Cruise UI" tone="primary" />
<CruiseAvatar alt="Guest" />
<CruiseAvatar src="https://res.cloudinary.com/demo/image/upload/missing-photo.jpg" alt="Musa Garba" name="Musa Garba" tone="auto" />
</Row>
<Row title="Status, sizes and square">
<CruiseAvatar name="Tunde Bello" tone="auto" size="sm" status="online" />
<CruiseAvatar name="Kemi Adeyemi" tone="auto" status="away" />
<CruiseAvatar name="Femi Ojo" tone="auto" size="lg" status="busy" />
<CruiseAvatar name="Zainab Musa" tone="auto" size="xl" status="offline" />
<CruiseAvatar name="Danfo Logistics" shape="square" tone="primary" size="lg" />
</Row>
<CruiseAvatarGroup people={team} max={5} label="Team" />
</div>
);
}
<span class="avatar avatar-lg">
<span class="avatar-fallback" role="img" aria-label="Idris Bankole">IB</span>
<img class="avatar-image" src="https://example.com/idris.jpg" alt="Idris Bankole" loading="lazy" decoding="async"
data-loading onload="this.removeAttribute('data-loading')" onerror="this.setAttribute('data-error', '')">
<span class="avatar-status" data-status="online" aria-hidden="true"></span>
<span class="visually-hidden">, online</span>
</span>
<span class="avatar" data-shape="square" data-tone="primary">
<span class="avatar-fallback" role="img" aria-label="Danfo Logistics">DL</span>
</span>
<div class="avatar-group" role="group" aria-label="Team">
<span class="avatar" data-tone="info"><span class="avatar-fallback" role="img" aria-label="Ada Okafor">AO</span></span>
<span class="avatar" data-tone="success"><span class="avatar-fallback" role="img" aria-label="Ngozi Eze">NE</span></span>
<span class="avatar avatar-more" role="img" aria-label="and 3 more">+3</span>
</div>Needs theme.css. Interactive parts (menus, tabs, drawers) also need the script in the spec for that component.
When to use it
When to use
Showing a person (or a team or brand, with the square shape) next to their name, comments or a list.
When not to
Product photos (use an image in a Card).
Props
CruiseAvatar
| Prop | Type | Default | Description |
|---|---|---|---|
name? | string | — | The person's name: the initials and the fallback's accessible name. |
initials? | string | — | Overrides the initials from `name` (max 2 characters). |
icon? | ReactNode | — | Fallback when there's no image, name or initials (default: Lucide user). |
size? | number | CruiseAvatarSize | "md" | 32 / 40 / 56 / 80px, or any number of pixels. |
shape? | "circle" | "square" | "circle" | Circle (default) or a square that follows var(--radius), for brands and teams. |
status? | CruiseAvatarStatus | — | — |
tone? | CruiseAvatarTone | "muted" | Background behind the initials; "auto" picks one from the name. |
imageComponent? | ComponentType<CruiseAvatarImageProps> | — | Renders the image with your component, e.g. next/image (add the host to images.remotePatterns in next.config). Gets src, alt, width, height. |
referrerPolicy? | HTMLAttributeReferrerPolicy | — | — |
crossOrigin? | "anonymous" | "use-credentials" | — | — |
className? | string | — | — |
src | string | undefined | — | — |
alt | string | — | — |
CruiseAvatarGroup
| Prop | Type | Default | Description |
|---|---|---|---|
people | CruiseAvatarPerson[] | — | — |
max? | number | — | How many avatars to show before the "+n" counter (all by default). |
size? | number | CruiseAvatarSize | "md" | — |
shape? | "circle" | "square" | "circle" | — |
tone? | CruiseAvatarTone | "auto" | — |
label? | string | "People" | Accessible name for the group, e.g. "Team". |
className? | string | — | — |
Using images from S3 / Cloudinary
- Any image URL works: S3, Cloudinary, Firebase Storage, Gravatar or your own server.
- Public URLs and signed URLs both work. For a private bucket, pass a signed URL; if it expires or returns 403, the initials show instead.
- To use next/image, pass imageComponent={Image} and add the image host to images.remotePatterns in next.config.
- If your site sends a Content-Security-Policy, add the image host to img-src.
Accessibility
- The image uses its alt text; the initials or icon fallback is role="img" named after the person.
- Next to a visible name, pass alt="" (and no name) so it isn't read twice.
- Status is said in words too (", online"), not only by the dot's colour.
- The fallback shows while the image loads and if it fails: never a broken-image icon.