Skip to content
Sign in
CollaborationPresence stackMenu

Presence stackPro

Who is in a thread, people and agents together, with live state drawn as shapes and a list of what everyone is doing.

ProA refund thread with people and agents coming and going: the stack, its line and the list.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/presence-stack

Usage

tsx
import { PresenceStack } from "@/components/ui/chatcn/pro/presence-stack"

<PresenceStack
  members={[
    { id: "ines", name: "Ines Okafor", state: "here", you: true },
    { id: "ledger", name: "Ledger", kind: "agent", state: "working", activity: "Matching charge 4471" },
    { id: "noor", name: "Noor Haddad", state: "idle", since: "8 m" },
  ]}
  onMemberSelect={(m) => follow(m.id)}
  selectLabel="Follow"
/>

People are circles with a photo or initials. Agents are squircles with their initials in mono, or the provider mark you pass as mark, and their names carry a quiet agent label, so the two never get confused without a robot glyph anywhere. Presence is drawn as a shape, so state reads in grayscale and forced colors: a dot is here, a ring is idle, a turning arc is an agent working and a small diamond is an agent waiting on someone. Away and offline dim the avatar instead. In the stack, being shown already means here, so plain here goes unmarked and only the states worth noticing carry a mark; the list marks everyone. The stack orders itself by urgency (waiting, working, here, idle, gone), shows max avatars and a count, and fans open a few pixels on hover or focus. The line beside it keeps the count whole and lets the names of working agents truncate first. PresenceList is the same list on its own, for a sidebar.

Examples

States

ProA crowd past the limit, small, only you, loading, the list on its own and a narrow line.

Props

PropTypeNotes
members(CollabMember & { state, activity?, since? })[]state: here, idle, away, offline, working, waiting
CollabMember{ id, name, kind?, avatar?, mark?, role?, you? }kind: "agent" draws a squircle and the agent label; mark is the provider mark you supply
maxnumberAvatars before the count. Default 4
size24 | 28 | 32Default 28
summarybooleanThe line beside the stack. Default true
onMemberSelect, selectLabel(member) => void, stringRows become buttons
open, defaultOpen, onOpenChangebooleanThe list popover
loading, localeboolean, stringNames join with Intl.ListFormat
labelsPartial<PresenceStackLabels>Every string, for translation

Labels

labels covers the trigger name, the list title, the summary and working lines, the overflow count, the agent and you tags and every state word (labels.states). Pass translated functions for strings with counts.

Keyboard

KeyAction
TabFocus the stack; it fans open
Enter or SpaceOpen the list
Tab inside the listMove between members when rows are buttons
EscClose the list

Accessibility

  • The stack is one button named with the count and the summary line.
  • Avatars are hidden from assistive tech; each row in the list says the name, the agent tag and the state in words.
  • Presence never relies on hue: every state has its own shape and its own word.

Motion

The fan slides each avatar 3 px per index in 200 ms. Someone joining grows in from 0.96 with a fade. Reduced motion keeps the fades and drops the movement. Nothing pulses.