Skip to content
iMessageUI

Documentation

Measured against the real thing.

Every size, colour, radius and timing in this registry comes from a capture of Messages on iOS 26 in the iPhone 17 Pro simulator, or macOS 26.5 Messages. The numbers and how they were measured live in references/SPEC.md. Anything without a native capture is marked unverified there and in the component itself, so you can tell what is reproduced from what is inferred.

Two gates

Did our rendering change?

Playwright screenshots every scenario checkpoint on both platforms and themes, in Chromium and WebKit. These baselines catch regressions in this implementation. They are not evidence of fidelity to Apple, and are never copied into the reference set.

Do we match Apple?

A lab page is diffed against the native capture it reconstructs, at the device pixel ratio of the capture. The tool writes reference, actual and diff images side by side and prints the mismatched-pixel ratio. Text anti-aliasing differs between a simulator and a browser; geometry and colour must not.

Labs

Install

bunx shadcn@latest add http://localhost:3100/r/index.json          # everything
bunx shadcn@latest add http://localhost:3100/r/ios-messages-app.json  # one item

Or register @imessage in your components.json and run bunx shadcn@latest add @imessage/conversation. Components install under components/imessage/ and use your own cn helper.

Blocks

Conversation

A complete, composable conversation for either platform: header, scrolling log with clusters and reactions, typing state, and a composer. Bring your own data and send handler.

"use client";

import { useState } from "react";
import { Conversation } from "@/components/imessage/conversation";
import type { Message } from "@/components/imessage/message-list";

export function Chat() {
  const [messages, setMessages] = useState<Message[]>([
    { id: "1", direction: "incoming", text: "Hey, you made it. đź‘‹", sentAt: Date.now() - 60_000 },
  ]);

  return (
    <Conversation
      platform="ios"
      name="Alex Morgan"
      messages={messages}
      onSend={(text) => setMessages(current => [
        ...current,
        { id: crypto.randomUUID(), direction: "outgoing", text, sentAt: Date.now() },
      ])}
    />
  );
}

iOS Messages app

The complete iOS 26 Messages app: list, conversation, New Message sheet, and the long-press overlay.

import { IosMessagesApp } from "@/components/imessage/ios-messages-app";

<IosMessagesApp
  contact={{ name: "Alex Morgan" }}
  messages={messages}
  composer={{ onSend: sendMessage }}
/>

macOS Messages app

The complete macOS 26 Messages window: sidebar, header, message log, composer, and the right-click menu.

import { MacMessagesApp } from "@/components/imessage/macos-messages-app";

<MacMessagesApp
  contact={{ name: "Alex Morgan" }}
  conversations={conversations}
  selectedId="alex"
  messages={messages}
  composer={{ onSend: sendMessage }}
/>

Components

Applications own transport, storage, uploads and real calls. Independent project, not affiliated with Apple.

Open the scenario lab