Shared Inbox Triage

The shared inbox

Every conversation has an owner and a clock

Nothing sits unclaimed. Each thread carries the person responsible, the site it belongs to, and how long the member has been waiting.

MemberOwnerWaitingStatus

Rowan Blake

Can I move Wednesday to the afternoon?

Bramble Health

Portrait of Priya NandanPriya Nandan
Waiting18 minWaiting on us

Amelia Frost

Referral letter has the wrong address on it

Kingsway Clinic

Portrait of Tom AshbyTom Ashby
Waiting2 hOpen

Noah Reyes

Invoice 4821 was paid twice

Harbour Practice

Portrait of Elena RourkeElena Rourke
Waiting41 minWaiting on us

Maya Coleman

Thanks, the new booking link worked

Bramble Health

Portrait of Tom AshbyTom Ashby
Waiting—Closed

Idris Warren

Does the reminder go to both parents?

Kingsway Clinic

Portrait of Priya NandanPriya Nandan
Waiting6 minOpen
About this block

Shared Inbox TriagePRO

A shared inbox laid out as hairline rows carrying the member, the subject, an assignee avatar, how long they have waited, and a tone-coded status pill, filtered by status chips with live counts.

Chat26: Shared Inbox Triage

A shared inbox laid out as hairline rows carrying the member, the subject, an assignee avatar, how long they have waited and a tone-coded status pill, filtered by status chips with live counts.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/chat26?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/chat26?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/chat26.tsx plus the badge23 and button21 components it uses for the eyebrow and the action.

Quick start

The installed file exports chat26Demo alongside the block: the exact props behind the preview above. Spread it to get a working inbox in one line.

tsx
import { Chat26, chat26Demo } from "@/components/beste/block/chat26";

export default function InboxPage() {
  return <Chat26 {...chat26Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Chat26 } from "@/components/beste/block/chat26";

export default function InboxPage() {
  return (
    <Chat26
      badge={{ label: "The shared inbox" }}
      heading="Every conversation has an owner and a clock"
      description="Nothing sits unclaimed."
      statusLabels={{ open: "Open", waiting: "Waiting on us", closed: "Closed" }}
      allLabel="Everything"
      columns={["Member", "Owner", "Waiting", "Status"]}
      conversations={[
        {
          member: "Rowan Blake",
          subject: "Can I move Wednesday to the afternoon?",
          status: "waiting",
          waiting: "18 min",
          site: "Bramble Health",
          assignee: {
            name: "Priya Nandan",
            avatar: { src: "/people/priya.jpg", alt: "Portrait of Priya Nandan" },
          },
        },
      ]}
      button={{ label: "See how triage works", href: "/product/inbox" }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }–Eyebrow above the hairline rule, rendered through Badge23
headingstring–Section heading in the left column of the header
descriptionstring–Supporting paragraph, right-aligned from md up
statusLabelsRecord<Status, string>–The word shown on each pill and filter chip
allLabelstring"All"Label for the unfiltered chip
columnsstring[][]Column headings, also reused as mobile row labels
conversationsConversation[][]Inbox rows in the order they should read
button{ label: string; href: string }–Outline action under the table
classNamestring–Extra classes for the outer section
ts
type Status = "open" | "waiting" | "closed";

type ActionLink = {
  label: string;
  href: string;
};

type Assignee = {
  name: string;
  avatar: { src: string; alt: string };
};

type Conversation = {
  member: string;
  subject: string;
  status: Status;
  waiting: string;
  assignee: Assignee;
  site: string;
};

Behavior notes

  • The three status filters are hard-coded from the Status union rather than derived from the data, so all three chips always appear, including ones with a count of zero. That is deliberate: a disappearing filter reads as a missing feature.
  • Counts are computed per render, so they always match what the filter will show.
  • statusLabels is used in both the chips and the row pills, so the two can never disagree. It falls back to the raw key when a label is missing.
  • Pills carry colour plus a word, never colour alone: emerald for open, amber for waiting, muted for closed.
  • The column header row is hidden lg:grid, so on mobile the waiting label comes from columns[2] rendered inline in that cell. Passing fewer than three column labels leaves it blank.
  • Because the header row is hidden below lg, its margin would collapse there. The rows container carries mt-8 lg:mt-0 to compensate.
  • The member cell uses min-w-0 with truncate on the subject, so a long subject ellipsizes rather than pushing the other columns out of the grid.
  • Rows are not links. This block shows the shape of a triage queue rather than navigating into it, so wiring rows to a thread view means wrapping them yourself.

More Chat blocks

View all Chat
PRO

chat22

Member Inbox And Thread

PRO

chat31

Two Sides Of One Conversation

PRO

chat23

Messaging Overview Split

Get Pro access