Avatar Picker

An avatar cluster that opens a searchable list of people — tick several to assign them, in a table cell or a card.

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/avatar-picker.json
Usage
Hand it everyone who can be picked and keep the picked ids in state.
TSImport
import { AvatarPicker } from "@/components/blocks/avatar-picker";
TSExample
import * as React from "react";
import { AvatarPicker } from "@/components/blocks/avatar-picker";

const people = [
  { id: "blake", name: "Blake Riley", src: "/images/avatar/blake-riley.jpg", description: "blake@thesqd.com" },
  { id: "benedict", name: "Benedict Doherty", src: "/images/avatar/benedict-doherty.jpg", description: "benedict@thesqd.com" },
  { id: "alisa", name: "Alisa Hester", src: "/images/avatar/alisa-hester.jpg", description: "alisa@thesqd.com" },
  { id: "jordan", name: "Jordan Lee", description: "jordan@thesqd.com" },
  { id: "sam", name: "Sam Carter", description: "sam@thesqd.com" },
];

export function TaskAssignees() {
  const [picked, setPicked] = React.useState<string[]>(["blake", "alisa"]);
  return (
    <AvatarPicker
      people={people}
      value={picked}
      onValueChange={setPicked}
      suggested={["jordan", "benedict"]}
    />
  );
}
Composition
A multiple Combobox whose trigger is the Avatar group.
AvatarPicker                     (Combobox, multiple)
├── trigger                       (Avatar people group, max + "+N" chip;
│                                  a dashed UserPlus circle when nobody is picked)
└── ComboboxContent
     ├── Input search             (the Inputs page's search field; searches name and description)
     ├── ComboboxEmpty
     └── ComboboxList
          └── ComboboxGroup × 3    (Selected, Suggested, Everyone — fixed when the list opens)
               └── ComboboxItem    (Avatar xs, name over description, check when picked)
Default
Two people picked and two suggested. The list opens on Selected, then Suggested, then Everyone; it stays open between picks.
Nobody picked
With no one assigned the trigger is a dashed add circle, the same size as a face.
Over the cap
Past `max` the cluster collapses into a `+N` chip; the list still shows everyone.
One owner
`single`: one face, one owner — for a checklist item or a task. A pick replaces them.
API Reference
Props for AvatarPicker and each AvatarPickerPerson.

AvatarPicker

PropTypeDefaultDescription
peopleAvatarPickerPerson[]—Everyone who can be picked.
valuestring[]—Picked ids, in order (controlled).
defaultValuestring[]—Initially picked ids (uncontrolled).
onValueChange(value: string[]) => void—Fires with the whole new list on every pick or un-pick.
singleboolean—One person at most: a pick replaces the owner and closes the list; picking them again clears it. `value` is still an array (zero or one id).
suggestedstring[]—Ids for a "Suggested" section between the picked people and everyone else, best first. Picked ids are skipped.
onOpenChange(open: boolean) => void—Fires when the list opens or closes — fetch `suggested` here.
sectionLabels{ selected?; suggested?; everyone? }"Selected" / "Suggested" / "Everyone"Section headings.
maxnumber3Faces shown before the `+N` chip.
sizeAvatarSize"xs"Size of the trigger's faces.
labelstring"Assign people"Accessible name for the trigger.
searchPlaceholderstring"Search people..."Placeholder for the search field.
emptyMessageReactNode"No one found."Shown when the search matches nobody.
disabledboolean—Disable the trigger.
classNamestring—Extra classes on the trigger button.

AvatarPickerPerson

PropTypeDefaultDescription
idstring—Stable key (email, user id). What `value` holds.
namestring—Name in the list; drives the initials fallback.
srcstring | null—Photo URL.
descriptionstring—Muted second line in the list, also searched.