Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/avatar.jsonUsage
import { Avatar, AvatarGroup } from "@/components/asciicn/avatar";
export function Owner() {
return <Avatar name="Ada Lovelace" status="online" />;
}
export function Photo() {
return <Avatar name="Buzz Aldrin" src="/team/buzz.jpg" size="lg" />;
}
export function Viewers({ names }: { names: string[] }) {
return (
<AvatarGroup max={4} aria-label={`${names.length} viewers`}>
{names.map((name) => (
<Avatar key={name} name={name} />
))}
</AvatarGroup>
);
}Without src, the avatar takes up to two initials from name ("Ada Lovelace" is AL, "deploy-bot" DB, "grace@navy.mil" G) and sets them in quadrant blocks (▘▝▖▗▀▄▌▐█), a pixel face whose solid 2×2 sub-cells stay legible even at 32px. They sit over a disc drawn in charset, Braille by default, whose mid tones are spread by ordered dithering. The disc is lit from the upper left, so it reads as a sphere, and it is tinted with a palette colour picked from name, so the same person always gets the same colour. A ring of blank samples around the letters keeps them clear of the disc.
With src, the photo is cover-fitted, area-averaged into the glyph grid, stretched to its full tonal range and drawn in any charset. In a light theme the tones flip, so dark parts of the photo get the dense glyphs. colorMode="truecolor" keeps the photo's colour per glyph.
The grid is always twice as many columns as rows, which makes it square. sm is 4 rows at 32px, md 6 rows at 48px, lg 8 rows at 72px and xl 12 rows at 112px. Pass a number for any size in pixels.
Examples
Photo portraits
The same photo at 160px in Braille, Dense 70, ANSI blocks, and Dense 70 in truecolor.
Group with a count
AvatarGroup overlaps each avatar by a quarter of its width, with a ring in outline between them. Past max, the rest collapse into a grey +n disc.
Members with presence
status sets a glyph in the corner. Each status has its own shape as well as its own colour: ● online, ◐ away, ■ busy, ○ offline. The bot uses shape="square" and a grey tint.
Props
Avatar
| Prop | Type | Default |
|---|---|---|
| name* Who it is. Gives the initials, the default tint and the accessible name. | Type: string | Default – |
| label Accessible name, when it should differ from name. | Type: string | Default name |
| src A photo to draw as a glyph portrait instead of initials. | Type: string | Default – |
| size 32, 48, 72 or 112px, or any size in pixels. | Type: "sm" | "md" | "lg" | "xl" | number | Default "md" |
| charset Glyphs to draw with. | Type: CharsetName | Default "braille" |
| chars A custom ramp from empty to dense. Wins over charset. | Type: string | Default – |
| colorMode With src: truecolor keeps the photo's colour per glyph. | Type: "mono" | "truecolor" | Default "mono" |
| color Tint of the disc or photo, a palette name or any CSS colour. | Type: AsciiColor | Default Picked from name |
| shape A disc, or a square that fills the whole grid. | Type: "circle" | "square" | Default "circle" |
| status A status glyph in the corner. | Type: "online" | "away" | "busy" | "offline" | Default – |
AvatarGroup
| Prop | Type | Default |
|---|---|---|
| children* The avatars, in order. | Type: ReactNode | Default – |
| max Show at most this many, then a +n count. | Type: number | Default All |
| size Size of every avatar in the group. | Type: "sm" | "md" | "lg" | "xl" | number | Default "md" |
| outline Colour of the ring between overlapping avatars. | Type: string | Default "var(--ascii-paper)" |
Notes
- Each avatar is
role="img"named bylabelorname, plus its status ("Ada Lovelace, online"). The glyphs are hidden from screen readers. Give the group anaria-labelsuch as "9 viewers"; the count reads "and 5 more". - The disc is computed without a canvas, so it renders on the server with the initials as plain text over it. After mount, the initials are redrawn in glyphs; the box keeps its size, so nothing shifts.
- Photos load with
crossOrigin="anonymous", so images from another origin need CORS headers to be sampled. Until a photo loads, the avatar shows the initials disc. - Every avatar is a few hundred glyphs of DOM text. The grid is sampled once per change of name, photo, charset or theme, never per frame.