--- title: Message description: Composable chat message layout with markdown body, optional avatar, actions, and attachments --- import MessageDefault from "@/components/nexus-ui/examples/message/default"; import MessageWithActions from "@/components/nexus-ui/examples/message/with-actions"; import MessageWithAvatar from "@/components/nexus-ui/examples/message/with-avatar"; import MessageWithAttachments from "@/components/nexus-ui/examples/message/with-attachments"; import MessageRichText from "@/components/nexus-ui/examples/message/rich-text"; Composable **user** and **assistant** chat turns via **`Message`**, **`MessageContent`**, **`MessageMarkdown`** ([Streamdown](https://github.com/vercel/streamdown)), **`MessageAvatar`**, **`MessageActions`**, and **[Attachments](/docs/components/attachments)**. ## Installation ```bash npx shadcn@latest add @nexus-ui/message ``` ```bash pnpm dlx shadcn@latest add @nexus-ui/message ``` ```bash yarn dlx shadcn@latest add @nexus-ui/message ``` ```bash bunx shadcn@latest add @nexus-ui/message ```

Copy and paste the following code into your project.

Update the import paths to match your project setup.

## Usage ```tsx keepBackground import { Message, MessageStack, MessageContent, MessageMarkdown, MessageActions, MessageActionGroup, MessageAction, MessageAvatar, } from "@/components/nexus-ui/message"; ``` ```tsx keepBackground Hello ``` ## Examples ### With Actions Use **`MessageActions`** and **`MessageActionGroup`** for a row of controls. **`MessageAction`** is a thin **Slot** for individual action buttons. ### With Avatar **`MessageAvatar`** composes shadcn **Avatar** / **AvatarImage** / **AvatarFallback** — pass **`src`**, **`alt`**, and optional **`fallback`** (and **`delayMs`** if needed). Place the avatar **after** **`MessageStack`** for **`from="user"`**, and **before** **`MessageStack`** for **`from="assistant"`**. ### With Attachments Render **`AttachmentList`** / **`Attachment`** inside **`MessageStack`** above **`MessageContent`**. Use **`readOnly`** on **`Attachment`** when showing files that were already sent (no remove control or progress). See **[Attachments](/docs/components/attachments)** for **`AttachmentMeta`** and variants. ### Rich Text (Markdown) **`MessageMarkdown`** renders markdown with **Streamdown** and shared prose-style classes. Pass a string as **`children`** (or a template literal) for headings, lists, code blocks, and links. ## Message and Streamdown ### How **MessageMarkdown** uses Streamdown It is a thin wrapper around **[Streamdown](https://github.com/vercel/streamdown)** with the same component props, so you can pass **`children`**, **`className`**, and other **`Streamdown`** options as needed. Nexus applies shared **prose-style** **`className`** tokens for headings, body text, links, lists, and inline code. **Shiki** highlights fenced code with **`github-light`** and **`github-dark`**. **Plugins** from **`@streamdown/code`**, **`@streamdown/math`**, **`@streamdown/mermaid`**, and **`@streamdown/cjk`** enable code blocks, math, diagrams, and CJK-friendly typography. A few **MDX components** are overridden—especially **tables** and **codeblocks**. Streamdown parses markdown into HTML that relies on utility classes shipped inside **`streamdown`** and those plugin packages. That model works well for **streaming** assistant output so you do not hand-build the markup. ### Fenced Code **`MessageMarkdown`** uses **`CodeBlock`** for **`components.code`** (`@/components/nexus-ui/codeblock`). **`@streamdown/code`** still handles highlighting—you are only swapping the **UI**. Use **`CodeBlock`** **`showTitleRow`** to show or hide the fenced-block title row (pass it on your **`components.code`** renderer). Turn line gutters on or off with Streamdown **`lineNumbers`**, which **`MessageMarkdown`** forwards like any other Streamdown prop. **`@nexus-ui/message`** ships **`message.tsx`** and **`codeblock.tsx`** in one install—there is no separate codeblock package. ### Tailwind **@source** after install Installing **Message** with the **shadcn CLI** should merge **Tailwind `@source`** lines into the CSS file from **`components.json`** so those classes are **not purged**. With a **manual** install, that merge does not run by default. After either path, **open your global CSS** and **confirm** the **`@source`** entries for **`streamdown`** and **`@streamdown/*`** are present, and that **`../node_modules`** still resolves to the project root from that file’s folder. If anything is missing or the depth is wrong, markdown can look unstyled or code and diagrams can break. ## Vercel AI SDK Integration Render [`useChat`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat) messages with **Message** by reading each **[`UIMessage`](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message)** **`parts`** array. Join **`text`** parts for **MessageMarkdown** (streaming updates apply as the SDK appends or grows **`TextUIPart`** content). See [Prompt Input](/docs/components/prompt-input#vercel-ai-sdk-integration) for a minimal **`POST /api/chat`** route with **`streamText`** and **`toUIMessageStreamResponse`**. For user turns that include uploads, map **`file`** parts to **[Attachments](/docs/components/attachments)** (or your own preview) in addition to text—**[Attachments](/docs/components/attachments#vercel-ai-sdk-integration)** covers **`sendMessage`** with **`files`**.

Install the AI SDK

```bash npm install ai @ai-sdk/react @ai-sdk/openai ```

Create your chat API route

Use the same handler as in the Prompt Input docs: **`messages: await convertToModelMessages(messages)`** and **`return result.toUIMessageStreamResponse()`**.

Map `messages` to Message

Use **`isTextUIPart`** from **`ai`** so you only aggregate **`type: "text"`** segments. Skip **`system`** turns unless you surface them deliberately. Assistant messages can also include **reasoning**, **tool**, **source**, and other part types—extend this loop when you need those in the UI. ```tsx "use client"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport, isTextUIPart, type UIMessage } from "ai"; import { Message, MessageStack, MessageContent, MessageMarkdown, } from "@/components/nexus-ui/message"; function textFromMessage(message: UIMessage) { return message.parts.filter(isTextUIPart).map((p) => p.text).join(""); } export default function ChatThread() { const { messages } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), }); return (
{messages .filter((m) => m.role !== "system") .map((m) => ( {textFromMessage(m)} ))}
); } ```
## API Reference ### Message Root of one chat turn: row for stack, avatar, and siblings; **`from`** sets alignment and is provided in context to **`MessageStack`**, **`MessageContent`**, and **`MessageActions`**. ### MessageStack Stacks bubble, attachments, and actions in a column; cross-axis alignment follows **`from`** on **`Message`** (user vs assistant). ### MessageContent Wraps the message body (e.g. markdown inside). **User** turns get a filled bubble; **assistant** turns stay visually light on the thread—both follow **`from`** on **`Message`**. ### MessageMarkdown Renders **markdown** with **[Streamdown](https://github.com/vercel/streamdown)**. Props are forwarded to **`Streamdown`**; values you pass replace the same keys on the underlying component (e.g. a new **`plugins`** object replaces the default bundle). Commonly used options: For **`remend`**, **`remarkPlugins`**, **`rehypePlugins`**, **`linkSafety`**, **`animated`**, **`caret`**, and the full prop list, see the [Streamdown configuration docs](https://streamdown.ai/docs/configuration). ### MessageActions A flex container for action buttons. Default **`justify-end`** (user) or **`justify-start`** (assistant); override with **`className`** (e.g. **`justify-between`**) for multiple groups. ### MessageActionGroup Groups related action buttons together with a horizontal layout. ### MessageAction A wrapper for individual action buttons. Supports polymorphism via `asChild`. ### MessageAvatar **shadcn** [**Avatar**](https://ui.shadcn.com/docs/components/avatar) with **`src`** / **`alt`** / optional **`fallback`**. Sibling to **`MessageStack`** in **`Message`**: after the stack for **user**, before for **assistant**.