Person
betaAn avatar and a name, with an optional meta line. The way to name a person anywhere.
Preview
size
with meta
strong
no photo
narrow
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/person
Use it for
- Naming a person anywhere: table cells, list rows, comments, cards, and popovers. A name always has its avatar beside it.
- meta adds a muted line under the name, like a role and a team.
- Set strong when the name is the row's title.
- A slot that takes a person, like a PeekCard fact or a table cell, takes a PersonValue, { person, photo, meta }, and renders Person from it. isPersonValue tells it apart from text.
Avoid it for
- A bare name with no avatar, in a place that names people. Use Person.
- A count of people. Use AvatarStack.
- A name that opens the person's details on hover. Use PeekPerson.
- Long meta text. It cuts off with an ellipsis on one line.
Props
- name
Required. The person's full name.
string
- src
Photo URL. The avatar shows initials until it loads.
string
- meta
A muted line under the name, like "Product lead · Design".
string
- size
The avatar's size, as Avatar's. The name is 11, 13, or 16px, and the meta line 11, 11, or 13px.
"sm" | "md" | "lg" · default "md"
- strong
The name in the strong weight.
boolean · default false
- className
Extra classes on the root. Later classes win.
string
- PersonValue
Type. How to pass a person into a slot. Render it as <Person name={value.person} src={value.photo} meta={value.meta} />.
{ person: string; photo?: string; meta?: string }
- isPersonValue
Function. True for a PersonValue, false for text and elements.
(value: unknown) => value is PersonValue
| Prop | Type | Default | What it does |
|---|---|---|---|
| name | string | - | Required. The person's full name. |
| src | string | - | Photo URL. The avatar shows initials until it loads. |
| meta | string | - | A muted line under the name, like "Product lead · Design". |
| size | "sm" | "md" | "lg" | "md" | The avatar's size, as Avatar's. The name is 11, 13, or 16px, and the meta line 11, 11, or 13px. |
| strong | boolean | false | The name in the strong weight. |
| className | string | - | Extra classes on the root. Later classes win. |
| PersonValue | { person: string; photo?: string; meta?: string } | - | Type. How to pass a person into a slot. Render it as <Person name={value.person} src={value.photo} meta={value.meta} />. |
| isPersonValue | (value: unknown) => value is PersonValue | - | Function. True for a PersonValue, false for text and elements. |
Accessibility
- The avatar is hidden from screen readers, so the name is read once.
- Renders plain <span> elements, so it reads in line inside a link, a button, or a table cell.
- The name and meta cut off with an ellipsis when there is no room. Give the full text somewhere, like the person's page or a peek.
- The meta line uses the muted text color, held to 4.5:1 on the page, cards, and popovers.
Examples
Name and role
<Person name="Sam Carter" meta="Product lead · Design" />A row's title
<Person name="Kate Clarke" src="/people/kate.jpg" strong />From a PersonValue
import { Person, isPersonValue, type PersonValue } from "@/components/ui/person";
function Cell({ value }: { value: string | PersonValue }) {
return isPersonValue(value) ? <Person name={value.person} src={value.photo} meta={value.meta} size="sm" /> : value;
}