Member Inbox And Thread

Messaging

The conversation stays on the record

Members message the practice, not a person's phone. Every thread sits on the member file, so whoever is on shift can pick it up.

Inbox

Rowan Blake

Care plan, week 6 · Replies usually within an hour

Morning. I need to move Thursday to the afternoon if anything is free.08:54
Morning Rowan. We have 14:30 or 15:15 on Thursday with Priya, both in the ground floor room.09:02
That works, thank you. Should I bring the previous notes?09:12
About this block

Member Inbox And ThreadPRO

A two-pane messaging panel: a selectable conversation list with unread dots on the left, and the chosen thread on the right with member and staff bubbles, timestamps, and a composer row.

Chat22: Member Inbox And Thread

Two-pane messaging panel: a selectable conversation list carrying snippets, times, and unread dots on the left, and the chosen thread on the right with member and staff bubbles, timestamps, and a composer row along the bottom.

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/chat22?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

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

This installs the block to components/beste/block/chat22.tsx plus the badge23 component it uses for the eyebrow.

Quick start

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

tsx
import { Chat22, chat22Demo } from "@/components/beste/block/chat22";

export default function Page() {
  return <Chat22 {...chat22Demo} />;
}

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

tsx
import { Chat22 } from "@/components/beste/block/chat22";

export default function Page() {
  return (
    <Chat22
      badge={{ label: "Messaging" }}
      heading="The conversation stays on the record"
      description="Members message the practice, not a person's phone."
      items={[
        {
          name: "Rowan Blake",
          role: "Care plan, week 6",
          snippet: "That works, thank you.",
          time: "09:12",
          unread: true,
          status: "Replies usually within an hour",
          messages: [
            { from: "member", text: "Can I move Thursday to the afternoon?", time: "08:54" },
            { from: "staff", text: "We have 14:30 or 15:15 with Priya.", time: "09:02" },
            { from: "member", text: "That works, thank you.", time: "09:12" },
          ],
        },
      ]}
      labels={{
        inboxLabel: "Inbox",
        composerPlaceholder: "Write a reply",
        sendLabel: "Send",
      }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }–Monospace eyebrow above the hairline rule, rendered via Badge23
headingstring–Section heading in the left column
descriptionstring–Supporting paragraph, right-aligned from md up
itemsConversation[][]Inbox entries, each carrying its own thread
labelsChat22Labels{}Inbox kicker and composer copy, see below
classNamestring–Extra classes for the outer <section>
ts
type Message = {
  from: "member" | "staff";
  text: string;
  time: string;
};

type Conversation = {
  name: string;
  role: string;
  snippet: string;
  time: string;
  unread?: boolean;
  status: string;
  messages?: Message[];
};

type Chat22Labels = {
  inboxLabel?: string;
  composerPlaceholder?: string;
  sendLabel?: string;
};

Behavior notes

  • The selected conversation is a single index in local state, starting at 0, and the thread pane reads everything from that entry, so the two panes can never drift apart.
  • from decides the side and the fill: staff messages align right on the accent, member messages align left on the muted surface. There is no avatar in the bubble, the header pane carries the identity instead.
  • Timestamps sit outside the bubble, under it, which keeps the accent bubble a clean shape and the time legible on both sides.
  • The unread dot is data, not state: it is drawn from unread on the conversation and opening a thread does not clear it, since read state belongs to your backend.
  • The composer is one uncontrolled input and a send button with no handler, and its label is a visually hidden <label> bound with useId, so the field is announced without a visible caption.
  • Snippets are truncated with truncate, so the inbox column keeps its width whatever the last message said.
  • The panel splits at lg. Below that the inbox stacks above the thread with a rule between them rather than becoming a scrolling sidebar.

Wiring the form up

This block ships the composer markup only; state, validation, and sending are yours to add. Our guide wires field markup like this to React Hook Form, TanStack Form, and Formisch on one field system.

More Chat blocks

View all Chat
PRO

chat31

Two Sides Of One Conversation

PRO

chat12

Conversational Thread with Reactions

PRO

chat23

Messaging Overview Split

Get Pro access