Message UI: React Attachment Components for Chat UI

Build rich PNG attachment cards for iMessage and WhatsApp with React components, live preview, and server-side rendering via Satori.

Message UI is a React component library and renderer that turns message attachment templates into PNG images for chat apps, bots, and agents.

You can use it when a plain text message is too limited. For example, a commerce bot can send an order card, a travel assistant can send a gate-change card, and a finance assistant can send a weekly spending summary as an image attachment.

Write a small component, preview it locally, then render the same template to a PNG for delivery through iMessage, WhatsApp, or a custom messaging flow.

Features

  • Layout primitives include Attachment, Row, Section, Text, Heading, Image, Avatar, and Divider.
  • Three chart components handle dense data cards: LineChart, DonutChart, and ActivityRings.
  • Local preview CLI with template search and instant hot reload on save.
  • PNG export via Satori and Resvg with predictable output paths.
  • SVG export through the same renderToSvg function.
  • Tailwind CSS wrapper for styling templates with the @message-ui/tailwind package.
  • Server-safe render functions for API routes, workers, or batch jobs.
  • MIT License.

Use cases

  • A chatbot sends order tracking cards with status milestones, courier info, and estimated delivery time.
  • Health and fitness agents deliver weekly coaching cards with recovery scores, trend lines, and key metrics as a formatted visual.
  • Travel apps push gate-change alerts with departure time, seat, and terminal details in a card format.

How To Use It

Install

Install the renderer, component primitives, and Tailwind wrapper:

npm install @message-ui/render @message-ui/components @message-ui/tailwind

The renderer package depends on Satori and Resvg for SVG and PNG output, and it uses React as a peer dependency. Use it in a Node.js server route, worker, script, or background job rather than in browser-only React code.

Basic Usage

Create an attachment template in an attachments folder:

// attachments/invoice-reminder.tsx
import { Attachment, Heading, Row, Text } from "@message-ui/components";
export default function InvoiceReminder() {
  return (
    <Attachment
      style={{
        width: 420,
        padding: 24,
        borderRadius: 28,
        backgroundColor: "#111827",
      }}
    >
      <Heading level={2} style={{ color: "#f9fafb", margin: 0 }}>
        Invoice due soon
      </Heading>
      <Row style={{ marginTop: 16 }}>
        <Text style={{ color: "#d1d5db", fontSize: 15 }}>
          Client: Northstar Studio
        </Text>
      </Row>
      <Text style={{ marginTop: 10, color: "#facc15", fontSize: 28, fontWeight: 700 }}>
        $1,840
      </Text>
      <Text style={{ marginTop: 10, color: "#9ca3af", fontSize: 14 }}>
        Due Friday. Send a reminder from the billing dashboard.
      </Text>
    </Attachment>
  );
}

Run the preview server:

npx message-ui dev --dir ./attachments

Render the same component to a PNG:

// scripts/render-invoice-reminder.tsx
import { writeFile } from "node:fs/promises";
import { renderToPng } from "@message-ui/render";
import InvoiceReminder from "../attachments/invoice-reminder";
const png = await renderToPng(<InvoiceReminder />, {
  width: 800,
  height: 800,
});
await writeFile("out/invoice-reminder.png", png);

More Examples

Export all templates from the CLI

The CLI supports a dev command for local preview and an export command for batch PNG generation. The default template folder is attachments, and the default export folder is out.

# Preview templates at a custom port.
npx message-ui dev --dir ./attachments --port 3001
# Export every template in the folder.
npx message-ui export --dir ./attachments --out ./out

Render a card from a Next.js route

Use a Node.js route because the renderer reads font files and uses native PNG rasterization through Resvg.

// app/api/share-card/route.tsx
import { renderToPng } from "@message-ui/render";
import InvoiceReminder from "@/attachments/invoice-reminder";
export const runtime = "nodejs";
export async function GET() {
  const png = await renderToPng(<InvoiceReminder />, {
    width: 800,
    height: 800,
    scale: 2,
  });
  return new Response(png, {
    headers: {
      "Content-Type": "image/png",
      "Cache-Control": "public, max-age=3600",
    },
  });
}

This pattern fits app routes, scheduled jobs, and messaging webhooks. Do not import renderToPng into a Client Component.

Add a small line chart to a status card

Message UI includes SVG-based chart components such as LineChart and DonutChart. The line chart accepts a numeric series, dimensions, optional labels, and color options.

// attachments/revenue-pulse.tsx
import { Attachment, Heading, LineChart, Text } from "@message-ui/components";
export default function RevenuePulse() {
  return (
    <Attachment
      style={{
        width: 460,
        padding: 24,
        borderRadius: 30,
        backgroundColor: "#0f172a",
      }}
    >
      <Heading level={2} style={{ color: "#f8fafc", margin: 0 }}>
        Revenue pulse
      </Heading>
      <Text style={{ marginTop: 8, color: "#94a3b8", fontSize: 14 }}>
        Last 6 weeks
      </Text>
      <LineChart
        series={[42, 48, 46, 55, 63, 71]}
        labels={["W1", "W2", "W3", "W4", "W5", "W6"]}
        width={410}
        height={150}
        color="#38bdf8"
        areaColor="rgba(56, 189, 248, 0.14)"
        style={{ marginTop: 18 }}
      />
      <Text style={{ marginTop: 16, color: "#e2e8f0", fontSize: 16 }}>
        Up 12.7% compared with the previous period.
      </Text>
    </Attachment>
  );
}

API Reference

PackagePurpose
@message-ui/componentsReact primitives for attachment templates.
@message-ui/renderrenderToSvg and renderToPng functions for image output.
@message-ui/tailwindTailwind wrapper for template styling.
@message-ui/previewLocal preview server used by the CLI.
@message-ui/climessage-ui dev and message-ui export commands.
APINotes
renderToSvg(element, options)Returns an SVG string from a React node.
renderToPng(element, options)Returns a PNG buffer from a React node.
width and heightRequired render options.
scaleOptional PNG density setting. The renderer uses 3 as the default scale.
pointScaleFactorOptional Satori layout setting for pixel-grid control.
RenderErrorError class thrown when Satori or PNG rasterization fails.

Available Components

CategoryComponentsPractical Use
LayoutAttachment, Row, Column, Section, Spacer, DividerStructure compact attachment cards.
TextText, HeadingAdd readable labels, summaries, totals, and status text.
MediaImage, AvatarAdd logos, thumbnails, profile images, and brand marks.
ListsList, ListItemDisplay checklist items, milestones, or compact summaries.
ChartsLineChart, DonutChart, ActivityRingsShow metrics inside image-based chat cards.

Alternatives and Related Resources

FAQs

Q: Can I use Message UI inside a Next.js App Router project?
A: Yes. Render PNGs from a server-side route, script, worker, or job. Use export const runtime = "nodejs" in a Route Handler because the renderer depends on Node.js APIs and PNG rasterization.

Q: Why should I avoid rendering Message UI templates in a Client Component?
A: The delivered output is a static PNG. The renderer also depends on server-side code, so client-side React state and event handlers do not belong in the final rendering path.

Q: Does the Tailwind wrapper replace inline styles?
A: Use inline styles for the most predictable result. The Tailwind wrapper exists, but the current source marks parts of the Tailwind-to-Satori styling pipeline as future work.

Q: Do I need to install all three packages?
A: Only @message-ui/render is required for server-side PNG export. Add @message-ui/components to get the layout primitives and @message-ui/tailwind if you want to use Tailwind utility classes inside your templates.

Q: Can I run renderToPng in the browser?
A: No. The renderer uses Satori and Resvg, which are Node.js-only dependencies. Call renderToPng from a server route, edge function, or background job.

Pontus Abrahamsson

Pontus Abrahamsson

Leave a Reply

Your email address will not be published. Required fields are marked *