Avatar
User avatar primitive. Located at apps/web/components/ui/avatar.tsx.
Usage
<Avatar name="Gani Sigit" size="md" color="#93a4c4" />
Renders a circular avatar with initials (first letters of first + last word of name). Color is the background.
Props
name— required. Used for initials and aria-label.size—sm(24px) |md(32px) |lg(48px). Defaultmd.color— optional background color. Default: derived from name hash.src— optional image URL. Falls back to initials on error.className— additional classes.
Examples
Initial-based (default)
<Avatar name="Ari Rachman" size="sm" />
Background color auto-derived from a hash of name. Ensures the same user always gets the same color across sessions.
With image
<Avatar
name="Gani Sigit"
size="lg"
src="https://.../avatar.jpg"
/>
Explicit color
<Avatar name="Gani" color="#93a4c4" />
Use user.avatarColor from the DB:
<Avatar name={user.name} color={user.avatarColor || undefined} />
Stacked
<div className="flex items-center">
{members.slice(0, 4).map((m, i) => (
<Avatar
key={m.id}
name={m.name}
color={m.avatarColor}
className={i > 0 ? "-ml-2" : ""}
/>
))}
{members.length > 4 && (
<div className="w-8 h-8 rounded-full bg-surface-2 text-text-muted text-xs font-medium flex items-center justify-center -ml-2">
+{members.length - 4}
</div>
)}
</div>
Initials logic
function initials(name: string): string {
const words = name.trim().split(/\s+/);
if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
return (words[0][0] + words[words.length - 1][0]).toUpperCase();
}
- "Gani Sigit" → "GS"
- "John" → "JO"
- "Gani M Sigit" → "GS" (first + last, middle ignored)
Don't
- Don't use as a clickable action — wrap in a Button or add click handler on the parent
- Don't skip the
nameprop — used for screen reader aria-label - Don't set size to arbitrary values via className — use the
sizeprop (ensures consistent ratio)