Peek Card
betaThe standard content of a peek: who or what, status, facts, the people behind a count, fixes, a note, and next actions.
Preview
person
people
2 to approve
- Approved · Design
fixes
Two people on call at once
- Move Josh to backup
- Swap with Kate
glance
Next workshop: Tue 29 Sep. The peek shows a glance: 3 facts and 2 actions.
choices
New members: 1 to approve. Esc puts the choices away first, and the next Esc closes the peek.
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/peek-card
Use it for
- As a HoverCard's content. Its title names the peek, and in a glance it shows up to 3 facts and 2 actions. People, fixes, and the note always show.
- Facts are [label, value, tone?] rows. Say who and when, like ['Back', 'Mon 19 Oct · HR'], so people know where a fact came from.
- A fact value that is a PersonValue, like { person: 'Steve Rowe' }, shows as a Person with a photo.
- people lists the people behind a count, like the 6 who have not replied, each with a status and an action like Remind.
- fixes lists ways to fix a problem, best first. The first gets the one-press button. detail says what happens.
- Give a press a result, like { label: 'Sent' }. The button slips to it in place. With onUndo, coming back shows Undo for 8 seconds, and ⌘Z undoes it too. Send the change in onCommit.
- Return a promise from onClick or onCommit, and give the result failed words. The button waits while it runs, and turns into Retry if it fails.
- Other controls that leave a result use useActionResult from tool-button, and resultFaces from this file for their faces. undoable, in tool-button, puts a change with no control, like a saved edit, on the page's ⌘Z stack.
- Put the key in shortcut. It shows in the button's tooltip only. Wire the key itself with ActionKeys.
Avoid it for
- A long form or a menu of choices. Use a Popover, a Menu, or a Dialog.
- A toast to repeat what a pressed button already says. The button carries the result.
- More than one solid action. The main action is the one solid button.
- A go action as the main action. Going somewhere is never the main action.
Props
- person
Who it is about: a name, or { person, photo }. Shows their avatar.
string | PersonValue
- photo
The person's photo, beside a name.
string
- icon
An icon for what is not a person, in a soft tile.
ReactNode
- iconTone
The icon tile's color role.
"neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"
- leading
Your own avatar or icon, in place of person or icon.
ReactNode
- title
Names the peek. Default: the person's name.
string
- subtitle
A muted line under the title, like "Workshop · Tue 29 Sep · 19:00".
string
- status
Colored status text at the top right. Neutral is muted.
{ label: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning" }
- facts
Label and value rows. A glance shows the first 3.
[label: string, value: ReactNode | PersonValue, tone?: PeekTone][]
- people
The people behind a count. group puts them under a heading. done is the record from app state, shown in place of the action. action can ask a question with choices, like a team.
{ person; photo?; meta?; group?; done?; status?; action? }[]
- peopleAction
The action for a row that has none, like Remind. A choice joins the result, like Approved · Support.
{ label; choices?; ask?; onClick?; result?: ActionResult }
- fixes
Fixes for a problem, best first. cta labels the button, default "Fix". Once pressed, doneDetail replaces detail.
{ label; detail?; doneDetail?; icon?; cta?; onClick?; result?: ActionResult }[]
- note
A short muted note.
ReactNode
- actions
Next actions on a thin lip at the foot. The first is the main, solid on the right, unless one sets emphasis. outline sits just left of it, and ghost on the left. all marks every person whose action has that label as done. dismiss closes the peek after the click.
{ label; icon?; shortcut?; emphasis?; go?; all?; dismiss?; href?; iconOnly?; onClick?; result?: ActionResult }[]
- more
The way to the full page. A plain button beside the main action, or a link under the actions when it has href.
{ label?; href?; onClick? }
- className
Extra classes on the card. Later classes win.
string
- ActionResult
Type, from tool-button. What a press leaves. label shows once pressed and doneLabel once the window ends. failed is what Retry says. seconds is the window, default 8. toast goes to the toast slot, only for what the button can't show.
{ label; doneLabel?; say?; failed?; onUndo?; undone?; onCommit?; seconds?; toast? }
| Prop | Type | Default | What it does |
|---|---|---|---|
| person | string | PersonValue | - | Who it is about: a name, or { person, photo }. Shows their avatar. |
| photo | string | - | The person's photo, beside a name. |
| icon | ReactNode | - | An icon for what is not a person, in a soft tile. |
| iconTone | "neutral" | "brand" | "danger" | "success" | "warning" | "neutral" | The icon tile's color role. |
| leading | ReactNode | - | Your own avatar or icon, in place of person or icon. |
| title | string | - | Names the peek. Default: the person's name. |
| subtitle | string | - | A muted line under the title, like "Workshop · Tue 29 Sep · 19:00". |
| status | { label: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning" } | - | Colored status text at the top right. Neutral is muted. |
| facts | [label: string, value: ReactNode | PersonValue, tone?: PeekTone][] | - | Label and value rows. A glance shows the first 3. |
| people | { person; photo?; meta?; group?; done?; status?; action? }[] | - | The people behind a count. group puts them under a heading. done is the record from app state, shown in place of the action. action can ask a question with choices, like a team. |
| peopleAction | { label; choices?; ask?; onClick?; result?: ActionResult } | - | The action for a row that has none, like Remind. A choice joins the result, like Approved · Support. |
| fixes | { label; detail?; doneDetail?; icon?; cta?; onClick?; result?: ActionResult }[] | - | Fixes for a problem, best first. cta labels the button, default "Fix". Once pressed, doneDetail replaces detail. |
| note | ReactNode | - | A short muted note. |
| actions | { label; icon?; shortcut?; emphasis?; go?; all?; dismiss?; href?; iconOnly?; onClick?; result?: ActionResult }[] | - | Next actions on a thin lip at the foot. The first is the main, solid on the right, unless one sets emphasis. outline sits just left of it, and ghost on the left. all marks every person whose action has that label as done. dismiss closes the peek after the click. |
| more | { label?; href?; onClick? } | - | The way to the full page. A plain button beside the main action, or a link under the actions when it has href. |
| className | string | - | Extra classes on the card. Later classes win. |
| ActionResult | { label; doneLabel?; say?; failed?; onUndo?; undone?; onCommit?; seconds?; toast? } | - | Type, from tool-button. What a press leaves. label shows once pressed and doneLabel once the window ends. failed is what Retry says. seconds is the window, default 8. toast goes to the toast slot, only for what the button can't show. |
Accessibility
- In a HoverCard, the title names the peek's dialog. On its own, the card has no role.
- A press says its result once, through one polite live region for the page. The button's name follows its state: the label, then the result, with "Press to undo." while a press undoes, then the record.
- A press that fails is named "Retry:" and what failed, so a voice command for Retry finds it.
- Undo arms only after the pointer leaves, or 1.2 seconds after a key or touch press, so a double press never undoes.
- ⌘Z or Ctrl+Z undoes the newest change still in its window. In a text field it stays the field's own undo.
- An icon-only action is named by its label, which shows in its tooltip. A shortcut key shows in the tooltip too.
- An action with choices sets aria-expanded and moves focus to the first choice. Esc puts the choices away and focus goes back to the action.
- Rows with a group sit in a list named by the group heading.
Examples
Who is going
<PeekCard
title="21 going"
subtitle="Workshop · Tue 29 Sep · 19:00"
people={[
{ person: "Sam Carter", meta: "Host", status: { label: "Going", tone: "success" } },
{ person: "Josh Tait", meta: "Engineer", status: { label: "No reply" } },
]}
peopleAction={{ label: "Remind", result: { label: "Sent" } }}
more={{ label: "See all 21", href: "/events/29-sep" }}
/>A person, in a peek
<HoverCard
content={
<PeekCard
person="Josh Tait"
subtitle="Engineer · Engineering"
status={{ label: "Out sick", tone: "danger" }}
facts={[["Back", "Mon 19 Oct · HR"], ["Next", "Team sync, Thu 10:00"]]}
actions={[{ label: "Message" }, { label: "Profile", go: true, href: "/people/josh-tait" }]}
/>
}
>
Josh Tait
</HoverCard>Fixes, best first
<PeekCard
title="Two people in one slot"
status={{ label: "Clash", tone: "warning" }}
fixes={[
{ label: "Move Josh to backup", detail: "Kate stays on call.", cta: "Move", result: { label: "Moved" } },
{ label: "Swap with Kate", detail: "Josh is on call, Kate is backup.", cta: "Swap", result: { label: "Swapped" } },
]}
/>A press that can fail
<PeekCard
title="Josh Tait"
actions={[
{
label: "Remind",
onClick: () => sendReminder("josh"),
result: { label: "Reminder sent", failed: "Couldn't send the reminder", onUndo: cancelReminder },
},
]}
/>