# Message



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";
import { Callout } from "@/components/callout";

Composable **user** and **assistant** chat turns via **`Message`**, **`MessageContent`**, **`MessageMarkdown`** ([Streamdown](https://github.com/vercel/streamdown)), **`MessageAvatar`**, **`MessageActions`**, and **[Attachments](/docs/components/attachments)**.

<DemoWithCode src="components/nexus-ui/examples/message/default.tsx">
  <MessageDefault />
</DemoWithCode>

Installation [#installation]

<Tabs items={["CLI", "Manual"]} framed={false}>
  <Tab value="CLI">
    <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
      <Tab value="npm">
        ```bash
        npx shadcn@latest add @nexus-ui/message
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm dlx shadcn@latest add @nexus-ui/message
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn dlx shadcn@latest add @nexus-ui/message
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx shadcn@latest add @nexus-ui/message
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab value="Manual">
    <Steps>
      <Step>
        <h3>
          Install the following dependencies:
        </h3>

        <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
          <Tab value="npm">
            ```bash
            npx shadcn@latest add button avatar tooltip kbd && npm install streamdown @streamdown/cjk @streamdown/code @streamdown/math @streamdown/mermaid radix-ui @radix-ui/react-slot @hugeicons/react @hugeicons/core-free-icons hast
            ```
          </Tab>

          <Tab value="pnpm">
            ```bash
            pnpm dlx shadcn@latest add button avatar tooltip kbd && pnpm add streamdown @streamdown/cjk @streamdown/code @streamdown/math @streamdown/mermaid radix-ui @radix-ui/react-slot @hugeicons/react @hugeicons/core-free-icons hast
            ```
          </Tab>

          <Tab value="yarn">
            ```bash
            yarn dlx shadcn@latest add button avatar tooltip kbd && yarn add streamdown @streamdown/cjk @streamdown/code @streamdown/math @streamdown/mermaid radix-ui @radix-ui/react-slot @hugeicons/react @hugeicons/core-free-icons hast
            ```
          </Tab>

          <Tab value="bun">
            ```bash
            bunx shadcn@latest add button avatar tooltip kbd && bun add streamdown @streamdown/cjk @streamdown/code @streamdown/math @streamdown/mermaid radix-ui @radix-ui/react-slot @hugeicons/react @hugeicons/core-free-icons hast
            ```
          </Tab>
        </Tabs>
      </Step>

      <Step>
        <h3>
          Copy and paste the following code into your project.
        </h3>

        <ComponentSource src="components/nexus-ui/message.tsx" title="components/nexus-ui/message.tsx" />

        <ComponentSource src="components/nexus-ui/codeblock.tsx" title="components/nexus-ui/codeblock.tsx" />
      </Step>

      <Step>
        <h3>
          Update import paths to match your project setup.
        </h3>
      </Step>
    </Steps>
  </Tab>
</Tabs>

Usage [#usage]

```tsx keepBackground
import {
  Message,
  MessageStack,
  MessageContent,
  MessageMarkdown,
  MessageActions,
  MessageActionGroup,
  MessageAction,
  MessageAvatar,
} from "@/components/nexus-ui/message";
```

```tsx keepBackground
<Message from="user">
  <MessageStack>
    <MessageContent>
      <MessageMarkdown>Hello</MessageMarkdown>
    </MessageContent>
  </MessageStack>
  <MessageAvatar src="/avatar.png" alt="You" fallback="Y" />
</Message>
```

Examples [#examples]

With Actions [#with-actions]

Use **`MessageActions`** and **`MessageActionGroup`** for a row of controls. **`MessageAction`** supports built-in tooltips via `tooltip` as either a string or object (`content`, optional `side`, optional `shortcut`).

<DemoWithCode src="components/nexus-ui/examples/message/with-actions.tsx">
  <MessageWithActions />
</DemoWithCode>

With Avatar [#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"`**.

<DemoWithCode src="components/nexus-ui/examples/message/with-avatar.tsx">
  <MessageWithAvatar />
</DemoWithCode>

With Attachments [#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.

<DemoWithCode src="components/nexus-ui/examples/message/with-attachments.tsx">
  <MessageWithAttachments />
</DemoWithCode>

Rich Text (Markdown) [#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.

<DemoWithCode previewClassName="h-[600px] justify-start overflow-y-auto" src="components/nexus-ui/examples/message/rich-text.tsx">
  <MessageRichText />
</DemoWithCode>

Message and Streamdown [#message-and-streamdown]

How MessageMarkdown uses 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 [#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 [#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.

<Callout type="warning">
  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 `@source` entries for `streamdown` and `@streamdown/*` are missing, markdown may render unstyled and code or diagram blocks can break.
</Callout>

Vercel AI SDK Integration [#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`**.

<Steps>
  <Step>
    <h3>
      Install the AI SDK
    </h3>

    ```bash
    npm install ai @ai-sdk/react @ai-sdk/openai
    ```
  </Step>

  <Step>
    <h3>
      Create your chat API route
    </h3>

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

  <Step>
    <h3>
      Map 

      `messages`

       to Message
    </h3>

    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 (
        <div className="flex flex-col gap-4">
          {messages
            .filter((m) => m.role !== "system")
            .map((m) => (
              <Message key={m.id} from={m.role === "user" ? "user" : "assistant"}>
                <MessageStack>
                  <MessageContent>
                    <MessageMarkdown>{textFromMessage(m)}</MessageMarkdown>
                  </MessageContent>
                </MessageStack>
              </Message>
            ))}
        </div>
      );
    }
    ```
  </Step>
</Steps>

API Reference [#api-reference]

Message [#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`**.

<TypeTable
  type={{
  from: {
    type: '"user" | "assistant"',
    description:
      "User vs assistant; alignment on Message and nested layout/styles via context.",
  },
  "aria-label": {
    type: "string",
    description:
      "Accessible name for the turn. Defaults to User message / Assistant message unless aria-labelledby is set.",
  },
  "aria-labelledby": {
    type: "string",
    description:
      "Optional id(s) of visible labels; when set, a default aria-label is not applied.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the message row.",
  },
}}
/>

MessageStack [#messagestack]

Stacks bubble, attachments, and actions in a column; cross-axis alignment follows **`from`** on **`Message`** (user vs assistant).

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the stack container.",
  },
}}
/>

MessageContent [#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`**.

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the bubble.",
  },
}}
/>

MessageMarkdown [#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:

<TypeTable
  type={{
  children: {
    type: "string",
    description: "Markdown source to render.",
  },
  className: {
    type: "string",
    description:
      "Additional CSS classes to apply to the Streamdown root element.",
  },
  mode: {
    type: '"streaming" | "static"',
    default: '"streaming"',
    description:
      "`streaming` for live token flow; `static` for finished content.",
  },
  isAnimating: {
    type: "boolean",
    default: "false",
    description:
      "Whether content is actively streaming (e.g. disables some controls).",
  },
  parseIncompleteMarkdown: {
    type: "boolean",
    default: "true",
    description:
      "Use the remend preprocessor for unfinished markdown while streaming.",
  },
  plugins: {
    type: "PluginConfig",
    description:
      "Math, Mermaid, Shiki, and CJK plugins. Replaces MessageMarkdown defaults when set.",
  },
  controls: {
    type: "ControlsConfig | boolean",
    description:
      "Show or hide table/code/Mermaid control buttons. Replaces MessageMarkdown defaults when set.",
  },
  components: {
    type: "Components",
    description:
      "Map markdown nodes to custom React elements. Replaces MessageMarkdown defaults when set.",
  },
  shikiTheme: {
    type: "[ThemeInput, ThemeInput]",
    default: "['github-light', 'github-dark']",
    description:
      "Light and dark syntax-highlighting themes for fenced code blocks.",
  },
}}
/>

For **`remend`**, **`remarkPlugins`**, **`rehypePlugins`**, **`linkSafety`**, **`animated`**, **`caret`**, and the full prop list, see the [Streamdown configuration docs](https://streamdown.ai/docs/configuration).

MessageActions [#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.

<TypeTable
  type={{
  className: {
    type: "string",
    description:
      "Additional CSS classes to apply to the actions container.",
  },
}}
/>

MessageActionGroup [#messageactiongroup]

Groups related action buttons together with a horizontal layout.

<TypeTable
  type={{
  className: {
    type: "string",
    description:
      "Additional CSS classes to apply to the group container.",
  },
}}
/>

MessageAction [#messageaction]

A wrapper for individual action buttons. Supports polymorphism via `asChild` and optional built-in tooltip rendering.

<TypeTable
  type={{
  asChild: {
    type: "boolean",
    default: "false",
    description: "Render as the child element instead of a div.",
  },
  className: {
    type: "string",
    description:
      "Additional CSS classes to apply to the action.",
  },
  tooltip: {
    type: 'string | { content?: string; side?: "top" | "right" | "bottom" | "left"; shortcut?: string }',
    description:
      "Tooltip config. String form maps to tooltip content. Object form supports content, side, and keyboard shortcut. If content is omitted, no tooltip is rendered.",
  },
}}
/>

MessageAvatar [#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**.

<TypeTable
  type={{
  src: {
    type: "string",
    description: "Image URL passed to AvatarImage.",
  },
  alt: {
    type: "string",
    default: '""',
    description: "AvatarImage alt text.",
  },
  fallback: {
    type: "React.ReactNode",
    description:
      "Optional. Shown inside AvatarFallback while loading or if the image errors.",
  },
  delayMs: {
    type: "number",
    description:
      "Optional. Passed to AvatarFallback (Radix delay before showing fallback).",
  },
  size: {
    type: '"default" | "sm" | "lg"',
    description: "Optional. shadcn Avatar size.",
  },
  className: {
    type: "string",
    description:
      "Additional CSS classes to apply to the Avatar root.",
  },
}}
/>
