Avatar
A Base UI avatar whose image is clipped inside its hand-drawn outline, with hatched initials as the fallback.
Installation
npx shadcn@latest add https://sketchcn.chuwii.com/r/avatar.jsonStyles
Avatar leans on these rules from sketch.css. The shadcn CLI installs the file with sketch-provider; copying the component by hand means copying them too.
@keyframes sketch-avatar-wobble {
0% {
transform: translateY(0) rotate(0deg);
}
35% {
transform: translateY(-5px) rotate(-5deg);
}
60% {
transform: translateY(-2px) rotate(4deg);
}
80% {
transform: translateY(-4px) rotate(-2deg);
}
100% {
transform: translateY(-3px) rotate(0deg);
}
}Read the whole file for the drawing variables, paper textures and reduced-motion rules.
Usage
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
export function Profile() {
return (
<Avatar>
<AvatarImage
src="https://api.dicebear.com/9.x/notionists/svg?seed=Chuwii"
alt="Chuwii"
/>
<AvatarFallback>CH</AvatarFallback>
</Avatar>
);
}Props
Avatar
Takes every Base UI Avatar.Root prop.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "default" | "lg" | "default" | Draws a 24px, 32px, or 40px ring. |
AvatarImage
Takes every Base UI Avatar.Image prop. It is clipped to the drawn ring, so the photo edge wobbles with the ink.
AvatarFallback
Takes every Base UI Avatar.Fallback prop. Renders hatched shading behind whatever you put inside, usually initials.
AvatarBadge
Takes every span prop. Sits at the bottom-right corner and follows the avatar size.
AvatarGroup
Takes every div prop. Overlaps its avatars, haloes each one in the background colour, and wobbles the hovered face as it lifts. The wobble is skipped under prefers-reduced-motion.
AvatarGroupCount
Takes every div prop. A drawn circle for the overflow count, sized from the avatars in the group.
Examples
Profile row
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
export function ProfileRow() {
return (
<div className="flex items-center gap-3">
<Avatar size="lg">
<AvatarImage
src="https://api.dicebear.com/9.x/notionists/svg?seed=Chuwii"
alt="Chuwii"
/>
<AvatarFallback>CH</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className="text-sm">Chuwii</span>
<span className="text-muted-foreground text-xs">
creator of sketchcn
</span>
</div>
</div>
);
}Missing portrait
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
export function MissingPortrait() {
return (
<Avatar size="lg">
<AvatarImage src="/nowhere.png" alt="Unknown teammate" />
<AvatarFallback>SK</AvatarFallback>
</Avatar>
);
}Team stack
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/components/ui/avatar";
const MEMBERS = ["Mali", "Anan", "Pim"];
export function TeamStack() {
return (
<AvatarGroup>
{MEMBERS.map((member) => (
<Avatar key={member}>
<AvatarImage
src={\`https://api.dicebear.com/9.x/notionists/svg?seed=\${member}\`}
alt={member}
/>
<AvatarFallback>{member.charAt(0)}</AvatarFallback>
</Avatar>
))}
<AvatarGroupCount>+8</AvatarGroupCount>
</AvatarGroup>
);
}Wobblier hand
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
export function ScribbledFallback() {
return (
<Avatar size="lg" className="[--sketch-roughness:2]">
<AvatarFallback className="[--sketch-bg-hachure-gap:3] [--sketch-bg-hachure-angle:45]">
SK
</AvatarFallback>
</Avatar>
);
}Animation
Inside an AvatarGroup, hovering a face lifts it out of the stack with the sketch-avatar-wobble keyframe from sketch.css. It ends raised, so the face stays up while the hover is held.
// on AvatarGroup
"*:data-[slot=avatar]:hover:z-10 *:data-[slot=avatar]:hover:[animation:sketch-avatar-wobble_450ms_ease-out_forwards]"sketch.css already disables it under prefers-reduced-motion.
@media (prefers-reduced-motion: reduce) {
[data-slot="avatar-group"] > [data-slot="avatar"]:hover {
animation: none;
}
}