The Future of Web Dev
The Future of Web Dev
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
renderToSvgfunction. - Tailwind CSS wrapper for styling templates with the
@message-ui/tailwindpackage. - 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
Table Of Contents
Install
Install the renderer, component primitives, and Tailwind wrapper:
npm install @message-ui/render @message-ui/components @message-ui/tailwindThe 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 ./attachmentsRender 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 ./outRender 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
| Package | Purpose |
|---|---|
@message-ui/components | React primitives for attachment templates. |
@message-ui/render | renderToSvg and renderToPng functions for image output. |
@message-ui/tailwind | Tailwind wrapper for template styling. |
@message-ui/preview | Local preview server used by the CLI. |
@message-ui/cli | message-ui dev and message-ui export commands. |
| API | Notes |
|---|---|
renderToSvg(element, options) | Returns an SVG string from a React node. |
renderToPng(element, options) | Returns a PNG buffer from a React node. |
width and height | Required render options. |
scale | Optional PNG density setting. The renderer uses 3 as the default scale. |
pointScaleFactor | Optional Satori layout setting for pixel-grid control. |
RenderError | Error class thrown when Satori or PNG rasterization fails. |
Available Components
| Category | Components | Practical Use |
|---|---|---|
| Layout | Attachment, Row, Column, Section, Spacer, Divider | Structure compact attachment cards. |
| Text | Text, Heading | Add readable labels, summaries, totals, and status text. |
| Media | Image, Avatar | Add logos, thumbnails, profile images, and brand marks. |
| Lists | List, ListItem | Display checklist items, milestones, or compact summaries. |
| Charts | LineChart, DonutChart, ActivityRings | Show metrics inside image-based chat cards. |
Alternatives and Related Resources
- Copy-paste React Components for AI Chat UI – Nexus UI
- Chat UI Components for React and shadcn/ui – chatcn
- AI Chatbot UI Blocks for shadcn/ui & Next.js Projects
- Build AI Chat Apps with assistant-ui React Component
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.




