Message

Render one message with its parts, registered tool and data UI, attachments, and hover state.

A message row reads its role from s.message and renders its content through MessagePrimitiveParts. Render it inside ThreadPrimitiveMessages, which scopes each row to a message. The registry styled thread uses the same primitives.

app/components/Message.vue
<script setup lang="ts">
import { defineComponent, h } from "vue";
import {
  AttachmentPrimitiveName,
  MessagePrimitiveAttachments,
  MessagePrimitiveParts,
  MessagePrimitiveRoot,
  useAuiState,
} from "@assistant-ui/vue";

const role = useAuiState((s) => s.message.role);

const TextPart = defineComponent({
  setup() {
    const text = useAuiState((s) => s.part.type === "text" ? s.part.text : "");
    return () => h("p", { style: { whiteSpace: "pre-line" } }, text.value);
  },
});

const ReasoningPart = defineComponent({
  setup() {
    const text = useAuiState((s) => s.part.type === "reasoning" ? s.part.text : "");
    return () => h("details", [h("summary", "Reasoning"), h("p", text.value)]);
  },
});

const ToolCallPart = defineComponent({
  setup() {
    const name = useAuiState((s) => s.part.type === "tool-call" ? s.part.toolName : "");
    return () => h("p", `Tool: ${name.value}`);
  },
});
</script>

<template>
  <MessagePrimitiveRoot
    class="flex flex-col gap-1"
    :class="role === 'user' ? 'items-end' : 'items-start'"
  >
    <div
      class="max-w-[80%] px-4 py-2"
      :class="role === 'user' ? 'bg-muted rounded-xl' : 'w-full'"
    >
      <MessagePrimitiveAttachments v-if="role === 'user'">
        <span class="text-xs"><AttachmentPrimitiveName /></span>
      </MessagePrimitiveAttachments>
      <MessagePrimitiveParts>
        <template #text><TextPart /></template>
        <template #reasoning><ReasoningPart /></template>
        <template #tool-call><ToolCallPart /></template>
      </MessagePrimitiveParts>
    </div>
  </MessagePrimitiveRoot>
</template>

The #tool-call slot is a fallback for calls without a registered tool UI. The tool UI guide covers registration.

How each part renders

MessagePrimitiveParts walks s.message.parts in order and gives each part its own scope. For each part, it chooses a renderer in this order:

  1. For tool-call, look up a registered tool UI by toolName. A renderText descriptor renders its resolved string or number directly. A registered component receives one tool prop. If renderText resolves to another kind of value, the part renders nothing.
  2. For a tool-call without a registered component or renderText, use the MCP app renderer when the part has a ui:// resource URI and an MCP app renderer is configured. It receives the same tool prop.
  3. For data, use the renderer registered under part.name, then the registered data fallback. The component receives one data prop.
  4. For text, use #text when present. Otherwise render a <p> with white-space: pre-line. A #default slot does not replace this text default.
  5. For any remaining part, use the slot named after part.type, then #default. With neither slot, render nothing and warn once per missing type in development.

Registered tool and data renderers take precedence over slots. A non-string, non-number renderText result ends rendering for that tool call, including the MCP and slot fallbacks.

Read the part inside a slot

The component rendered by a part slot reads its current s.part with useAuiState, as TextPart and ToolCallPart do above. Narrow on s.part.type before using fields specific to that part.

Part typeKey fields
texttext, optional id
reasoningtext, optional id, unstable_summary, timing
tool-calltoolCallId, toolName, args, optional result, approval, mcp
dataname, data, optional id
sourcesourceType, id, and url or document title and mediaType
imageimage, optional filename, id
filedata, mimeType, optional filename, sourceType, id
generative-uispec, optional id
audioaudio.data, audio.format; deprecated user part

s.part.status.type reports running, complete, or incomplete. A tool call can also report requires-action; check its reason when presenting an input or approval control. See State and actions for state access.

Attachments on user messages

MessagePrimitiveAttachments repeats its default slot for user attachments and scopes s.attachment to each one. It renders no entries for assistant messages. While a sent user message is still represented by its submission, it reads s.message.submission.attachments; otherwise it reads s.message.attachments. See Attachments for rendering one attachment.

Hover and identity

MessagePrimitiveRoot sets data-message-id on its <div> from s.message.id. Mouse enter and leave update s.message.isHovering. It also detects when it mounts under the pointer, so hover state becomes true without waiting for another mouse move. The state is cleared when the root is disposed.

Parts

MessagePrimitiveRoot

Renders a <div> around one scoped message. Attributes and listeners pass to that element. Its default slot is the message content; it has no props.

MessagePrimitiveParts

Renders each message part in order without a wrapper element. Its slots choose the content for parts that do not have a registered renderer.

SlotRenders
#textText parts instead of the default <p>.
#reasoningReasoning parts.
#tool-callTool calls without a registered tool or MCP app renderer.
#dataData parts without a registered named or fallback renderer.
#source, #image, #file, #generative-ui, #audioThe corresponding part types.
#defaultAny non-text part without a named slot or registered renderer.

It has no props. Parts use stable keys from their unique ids where available; otherwise the key uses their type and position.

MessagePrimitiveAttachments

Renders its default slot once per user attachment without a wrapper element. Each iteration has an attachment scope. It has no props.

PartByIndexProvider

Scopes its default slot to one part of the current message, so descendants read it through s.part.

PropTypeDefaultDescription
indexnumberRequiredZero-based position in s.message.parts.

MessageByIdProvider

Scopes its default slot to a thread message by identity. Descendants read s.message and that message's edit composer through s.composer.

PropTypeDefaultDescription
idstringRequiredMessage id in the current thread.

ToolUIProps

A registered Vue tool component declares one tool prop of this shape. See Tool UI.

FieldTypeDescription
partTool call part stateThe current tool call, including its status.
addResultFunctionAdd a result to a tool call awaiting one.
resumeFunctionResume a tool call waiting for human input.
respondToApprovalFunction returning Promise<void>Submit a server approval response.
unstable_recordInteractionOptional function returning Promise<void>Record a user interaction when the runtime exposes it.

DataUIProps

A registered Vue data component declares one data prop of this shape.

FieldTypeDescription
partData part state with data: TThe current data part, including its name and payload.

Compared with React

Vue has no counterpart for React's MessagePrimitive.If, MessagePrimitive.Quote, the grouped parts components, or the MessagePartPrimitive text and image parts. Use AuiIf for conditions, Error for failed responses, and slots with useAuiState for part content.