iMessage UIv0.1.0

Components

IMessageThread

The ready-made container: token scope + a scrollbar-less scroll area (iMessage has none) + header/footer slots, with the footer riding above the mobile keyboard via useKeyboardInset. The three things every chat surface re-implements, bundled.

2 People
Riley
scroll area has no scrollbar, like the real thing
so it's just… the whole chat?
"use client";

import { useState } from "react";
import {
  ChatBubble,
  ChatHeader,
  IMessageThread,
  InputBar,
} from "imessage-ui";
import type { ChatHeaderBot } from "imessage-ui";

const BOTS: ChatHeaderBot[] = [
  { id: "riley", avatar: { initial: "R", gradient: ["#FF9FB2", "#E0506E"] } },
  { id: "devon", avatar: { initial: "D", gradient: ["#7B8FFF", "#5057E0"] } },
];

/** The whole phone: header slot + scrollable thread + footer slot. */
export default function ThreadDemo() {
  const [draft, setDraft] = useState("");
  const [sent, setSent] = useState<string[]>(["so it's just… the whole chat?"]);

  return (
    <IMessageThread
      followKeyboard={false}
      className="h-96 w-full max-w-sm overflow-hidden rounded-2xl"
      header={<ChatHeader bots={BOTS} title="the group chat" embedded />}
      footer={
        <InputBar
          embedded
          value={draft}
          onChange={setDraft}
          onSend={text => {
            setSent(prev => [...prev, text]);
            setDraft("");
          }}
        />
      }
    >
      <div className="flex flex-col gap-1 px-3 py-2">
        <ChatBubble variant="received" text="scroll area has no scrollbar, like the real thing" senderName="Riley" avatar={BOTS[0].avatar} />
        {sent.map((text, i) => (
          <ChatBubble key={i} variant="sent" text={text} isNewlySent={i === sent.length - 1 && i > 0} />
        ))}
      </div>
    </IMessageThread>
  );
}

Usage

import { IMessageThread, ChatHeader, InputBar } from "imessage-ui";

<IMessageThread
  header={<ChatHeader bots={bots} embedded />}
  footer={<InputBar embedded value={v} onChange={setV} onSend={send} />}>
  {messages}
</IMessageThread>

API reference

PropTypeDefaultDescription
children*ReactNodeThe message list.
headerReactNodeHeader slot — ChatHeader or LiquidGlassHeader.
footerReactNodeFooter slot — usually an embedded InputBar.
followKeyboardbooleantrueLift the footer above the on-screen keyboard (writes --kb-height on the root).
scrollRefRefObject<HTMLDivElement | null>Ref to the scroll area, for auto-scroll-to-bottom.
variant"default" | "glass""default"Same as IMessageScreen.
wallpaperstringChat wallpaper behind the messages.
className / stylestring / CSSPropertiesPassthrough to the root.