Loading...
Loading...
An avatar component that displays user initials in a colored circle when no image is provided. Supports custom colors, sizes, and image fallback.
import { useState } from "react";
type AvatarSize = "sm" | "md" | "lg" | "xl";
interface AvatarInitialsProps {
src?: string;
alt?: string;
name?: string;
size?: AvatarSize;
bgColor?: string;
}
const sizeClasses: Record<AvatarSize, string> = {
sm: "w-8 h-8 text-xs",
md: "w-10 h-10 text-sm",
lg: "w-14 h-14 text-lg",
xl: "w-20 h-20 text-2xl",
};
function getInitials(name: string): string {
const parts = name.trim().split(/\s+/);
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
return parts[0]?.slice(0, 2).toUpperCase() || "?";
}
function stringToColor(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) hash = str.charCodeAt(i) + ((hash << 5) - hash);
const hue = hash % 360;
return `hsl(${hue}, 55%, 55%)`;
}
export function AvatarInitials({
src,
alt = "",
name = "User",
size = "md",
bgColor,
}: AvatarInitialsProps) {
const [imgError, setImgError] = useState(false);
const color = bgColor || stringToColor(name);
if (src && !imgError) {
return (
<img
src={src}
alt={alt || name}
onError={() => setImgError(true)}
className={`${sizeClasses[size]} rounded-full object-cover ring-2 ring-white`}
/>
);
}
return (
<div
className={`${sizeClasses[size]} rounded-full flex items-center justify-center font-bold text-white ring-2 ring-white shrink-0`}
style={{ backgroundColor: color }}
title={name}
aria-label={alt || name}
>
{getInitials(name)}
</div>
);
}