Person
PersonAvatar and PersonInfo are the standard way to render a person anywhere in the product. They guarantee a single visual language: one uppercase initial (name, falling back to email, then ?), a deterministic hash color (id, falling back to email, then name), and photos rendered with referrerPolicy="no-referrer".
Usage
The canonical person row: a lg avatar with the name and email stacked next to it.
import { PersonAvatar, PersonInfo } from '@jamie/ui/person'
const people = [
{
id: 'u1',
name: 'Egor Spirin',
email: 'egor@meetjamie.ai',
imageUrl: 'https://github.com/espj.png'
},
{ id: 'u2', name: 'Charles Peykov', email: 'charles@meetjamie.ai', imageUrl: null },
{ id: 'u3', name: 'Leslie-Alexandre Denis', email: 'leslie@meetjamie.ai', imageUrl: null }
]
export function PersonDemo() {
return (
<div className="flex w-72 flex-col">
{people.map((person) => (
<div key={person.id} className="flex items-center gap-2 rounded-sm px-1 py-1.5">
<PersonAvatar
size="lg"
name={person.name}
email={person.email}
imageUrl={person.imageUrl}
colorKey={person.id}
/>
<PersonInfo name={person.name} email={person.email} />
</div>
))}
</div>
)
}Sizes
PersonAvatar comes in six sizes: xs, sm, md, lg, xl, and 2xl. Use xs/sm for inline chips and stacks, md for menu rows, lg for list rows, and xl/2xl for tables and profile cards.
import { PersonAvatar } from '@jamie/ui/person'
const sizes = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const
export function PersonAvatarSizesDemo() {
return (
<div className="flex items-center gap-3">
{sizes.map((size) => (
<PersonAvatar
key={size}
size={size}
name="Egor Spirin"
email="egor@meetjamie.ai"
colorKey="u1"
/>
))}
</div>
)
}Fallbacks
When no image is available, the avatar shows the person's initial on a color derived from colorKey (pass a stable person or user id). Without a colorKey, the color falls back to the email, then the name, so the same person keeps the same color across surfaces. With no identity at all, a neutral ? is shown.
import { PersonAvatar } from '@jamie/ui/person'
export function PersonAvatarFallbackDemo() {
return (
<div className="flex items-center gap-3">
<PersonAvatar size="xl" name="Egor Spirin" imageUrl="https://github.com/espj.png" />
<PersonAvatar size="xl" name="Charles Peykov" colorKey="u2" />
<PersonAvatar size="xl" email="no-name@meetjamie.ai" />
<PersonAvatar size="xl" />
</div>
)
}Group
Use PersonAvatarGroup to stack multiple avatars with size-aware overlap and rings, and PersonAvatarGroupCount for overflow.
import { PersonAvatar, PersonAvatarGroup, PersonAvatarGroupCount } from '@jamie/ui/person'
const people = [
{ id: 'u1', name: 'Egor Spirin', imageUrl: 'https://github.com/espj.png' },
{ id: 'u2', name: 'Matt Milne', imageUrl: null },
{ id: 'u3', name: 'Leslie-Alexandre Denis', imageUrl: null }
]
export function PersonAvatarGroupDemo() {
return (
<div className="flex items-center gap-8">
<PersonAvatarGroup size="sm">
{people.map((person) => (
<PersonAvatar
key={person.id}
size="sm"
name={person.name}
imageUrl={person.imageUrl}
colorKey={person.id}
/>
))}
<PersonAvatarGroupCount size="sm">+3</PersonAvatarGroupCount>
</PersonAvatarGroup>
<PersonAvatarGroup size="md">
{people.map((person) => (
<PersonAvatar
key={person.id}
size="md"
name={person.name}
imageUrl={person.imageUrl}
colorKey={person.id}
/>
))}
<PersonAvatarGroupCount size="md">+3</PersonAvatarGroupCount>
</PersonAvatarGroup>
</div>
)
}