Two Sides Of One Conversation

Both sides

The same conversation, seen from two chairs

Your team gets the record and the history. The member gets one link that opens on whatever phone they already have.

Soft blue gradient backdrop
R

Rowan Blake

Waiting on us

Could I move Wednesday to the afternoon?
Yes, 15:15 is free with Dr Frost. Shall I take it?
Perfect, thank you.

What your team sees

The thread, on the record

Everything the member has ever asked, sitting against their appointments, notes, and invoices rather than in a separate app.

  • Full history without leaving the record
  • Saved replies that fill themselves in
  • Handover between shifts with nothing forwarded
Deep green gradient backdrop
09:41

Appointment

Moved

Wednesday, 14 May · 15:15

WithDr Amelia Frost
RoomClinic 2, ground floor
BringReferral letter
WasWednesday 09:00

What the member sees

One link, no account

A page with the room, the clinician, and what to bring on it, plus the two buttons they actually need.

  • Nothing to install and nothing to log into
  • Move or cancel without ringing the practice
  • Readable at arm's length on an old phone

Four messages, one afternoon, and nobody had to explain the situation twice. That is the whole feature.

See messaging end to end
About this block

Two Sides Of One ConversationPRO

A messaging section splitting a hairline matrix between the staff view and the member view, each half floating its own live asset above a title and a list of what that side gets.

Chat31: Two Sides Of One Conversation

A messaging section splitting a hairline matrix between the staff view and the member view, each half floating its own live asset above a title and a list of what that side gets.

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

Base UI flavor

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

This installs the block to components/beste/block/chat31.tsx, the browser35 and chat33 pieces it floats one per side (installed to components/beste/piece/{name}.tsx), and the badge23 and button21 components it uses for the eyebrow and the action.

Quick start

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

tsx
import { Chat31, chat31Demo } from "@/components/beste/block/chat31";

export default function MessagingPage() {
  return <Chat31 {...chat31Demo} />;
}

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

tsx
import { Browser35 } from "@/components/beste/piece/browser35";
import { Chat31 } from "@/components/beste/block/chat31";
import { Chat33 } from "@/components/beste/piece/chat33";

export default function MessagingPage() {
  return (
    <Chat31
      badge={{ label: "Both sides" }}
      heading="The same conversation, seen from two chairs"
      description="Your team gets the record and the history. The member gets one link."
      staff={{
        kicker: "What your team sees",
        title: "The thread, on the record",
        description: "Everything the member has ever asked, sitting against their appointments.",
        points: ["Full history without leaving the record", "Saved replies that fill themselves in"],
        media: (
          <Chat33
            name="Rowan Blake"
            status="Waiting on us"
            items={[{ from: "them", text: "Could I move Wednesday to the afternoon?" }]}
          />
        ),
        image: { src: "/backdrops/blue.jpg", alt: "Soft blue gradient backdrop" },
      }}
      member={{
        kicker: "What the member sees",
        title: "One link, no account",
        description: "A page with the room, the clinician, and what to bring on it.",
        points: ["Nothing to install and nothing to log into", "Move or cancel without ringing"],
        media: (
          <Browser35
            time="09:41"
            title="Appointment"
            status="Moved"
            headline="Wednesday, 14 May · 15:15"
            rows={[{ label: "With", value: "Dr Amelia Frost" }]}
          />
        ),
        image: { src: "/backdrops/green.jpg", alt: "Deep green gradient backdrop" },
      }}
      closing="Four messages, one afternoon, and nobody had to explain the situation twice."
      button={{ label: "See messaging end to end", href: "/product/messaging" }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }Eyebrow above the hairline rule, rendered through Badge23
headingstringSection heading in the left column of the header
descriptionstringSupporting paragraph, right-aligned from md up
staffSideThe left half, showing the internal view
memberSideThe right half, showing the customer view
closingstringParagraph beside the closing action
button{ label: string; href: string }Closing action
classNamestringExtra classes for the outer section
ts
type ActionLink = {
  label: string;
  href: string;
};

type TileImage = {
  src: string;
  alt: string;
};

type Side = {
  kicker: string;
  title: string;
  description: string;
  points: string[];
  media: ReactNode;
  image?: TileImage;
};

Behavior notes

  • The two halves are separate named props rather than an array, so each side has a fixed meaning and always renders in the same position. Either can be omitted and the other still fills its own grid track.
  • Both sides share the same Side shape, which is what keeps the two columns visually symmetrical: same kicker, same title scale, same list treatment, with only the asset and the copy differing.
  • The demo deliberately shows the same appointment from both sides, staff thread and member link. Keeping the two in sync is authoring work, since the block does not derive one from the other.
  • The matrix lines are the gap, not borders: gap-px over a bg-border container with bg-card cells, and the container's overflow-hidden clips the corners so it reads as one panel.
  • Point lists carry border-t with no closing rule, so each list flows into the card's own padding rather than closing itself off.
  • Cells are flex flex-col, so a pair with different amounts of copy still fills the row evenly.
  • The split is lg:grid-cols-2 only, so the two halves stack full width on tablet, where side by side would squeeze both tiles.

More Chat blocks

View all Chat
PRO

chat23

Messaging Overview Split

A messaging section pairing a light heading, a hairline table of response figures, and two actions with an image tile that floats a live conversation thread.

PRO

chat22

Member Inbox And Thread

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.

PRO

chat27

Inverted Out Of Hours Panel

A support section built as one dark panel, with a light heading, three hairline promises about who answers, two actions, and a bordered column floating a live on-call conversation.

PRO

chat28

Conversation Handover Trail

One member conversation walked through as timestamped hairline rows inside a bordered panel, each naming what happened and either the person who picked it up or the step that ran on its own.

PRO

chat12

Conversational Thread with Reactions

Chat message thread with rich formatting, copy and thumbs-up/down action buttons on AI messages, and time-grouped message sections. Features hover-revealed action bars and smooth animations.

PRO

chat14

Side-by-Side AI Model Comparison

Compare responses from two different AI models side by side. Features a shared prompt input, dual response panels with model badges, and rating buttons for each response.