component — display
Avatar
Displays a user image with a graceful initials fallback, styled with the quebi design system. Circular or square, with a full xs–9xl size scale.
- avatar
- image
- user
- profile
- initials
04 examples
Image
An avatar backed by a user photo. The image is clipped to the avatar box.
Initials fallback
With no image, initials render in the muted ink on the raised ground.
Square
Square variant via isSquare, for product or org logos.
Sizes
The scale runs xs through 9xl; here is a representative slice.
source
avatar.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
import { cn } from "@/lib/utils"
/**
* Avatar — quebi design system
*
* Renders a user image, falling back to initials in the muted ink on the
* raised ground. Circular by default — a face is round by nature — or a
* squarer mark via `isSquare`, at the mark radius (`--q-radius-mark`, square
* under `.quebi-editorial`). The full size scale runs xs → 9xl, driven by a CSS
* variable so the image and the initials SVG always track the same box. The
* edge is a hairline, never a drop shadow.
*/
export interface AvatarProps {
src?: string | null
initials?: string
alt?: string
className?: string
isSquare?: boolean
size?:
| "xs"
| "sm"
| "md"
| "lg"
| "xl"
| "2xl"
| "3xl"
| "4xl"
| "5xl"
| "6xl"
| "7xl"
| "8xl"
| "9xl"
}
export function Avatar({
src = null,
isSquare = false,
size = "md",
initials,
alt = "",
className,
...props
}: AvatarProps & React.ComponentPropsWithoutRef<"span">) {
return (
<span
data-slot="avatar"
{...props}
className={cn(
"inline-grid size-(--avatar-size) shrink-0 select-none align-middle",
"bg-quebi-raised text-quebi-fg-muted",
"outline outline-1 -outline-offset-1 outline-quebi-hairline",
"*:col-start-1 *:row-start-1 *:size-(--avatar-size)",
size === "xs" && "[--avatar-size:--spacing(5)]",
size === "sm" && "[--avatar-size:--spacing(6)]",
size === "md" && "[--avatar-size:--spacing(8)]",
size === "lg" && "[--avatar-size:--spacing(10)]",
size === "xl" && "[--avatar-size:--spacing(12)]",
size === "2xl" && "[--avatar-size:--spacing(14)]",
size === "3xl" && "[--avatar-size:--spacing(16)]",
size === "4xl" && "[--avatar-size:--spacing(20)]",
size === "5xl" && "[--avatar-size:--spacing(24)]",
size === "6xl" && "[--avatar-size:--spacing(28)]",
size === "7xl" && "[--avatar-size:--spacing(32)]",
size === "8xl" && "[--avatar-size:--spacing(36)]",
size === "9xl" && "[--avatar-size:--spacing(42)]",
isSquare
? "rounded-(--q-radius-mark) *:rounded-(--q-radius-mark)"
: "rounded-full *:rounded-full",
className,
)}
>
{initials &&
(alt ? (
<svg
className="size-full select-none fill-current p-[5%] text-[48px] font-medium uppercase"
viewBox="0 0 100 100"
>
<title>{alt}</title>
<text
x="50%"
y="50%"
alignmentBaseline="middle"
dominantBaseline="middle"
textAnchor="middle"
dy=".125em"
>
{initials}
</text>
</svg>
) : (
<svg
className="size-full select-none fill-current p-[5%] text-[48px] font-medium uppercase"
viewBox="0 0 100 100"
aria-hidden="true"
>
<text
x="50%"
y="50%"
alignmentBaseline="middle"
dominantBaseline="middle"
textAnchor="middle"
dy=".125em"
>
{initials}
</text>
</svg>
))}
{src && <img className="size-full object-cover object-center" src={src} alt={alt} />}
</span>
)
}