Fully Headless UI
Build any UI — chat or not — on top of the CopilotKit primitives with zero UI opinions.
This feature (headless-complete) hasn't been tagged in any MS Agent Harness (.NET) cell yet. Try CopilotKit's Built-in Agent, LangGraph (Python), LangGraph (TypeScript).
What is this?#
A headless UI gives you full control over the chat experience. You bring your own components, layout, and styling while CopilotKit handles agent communication, message management, tool-call rendering, and streaming. No <CopilotChat>, no slot overrides, just your components composed on top of the low-level hooks.
When should I use this?#
Use headless UI when:
- The slot system isn't enough: you need a completely different layout.
- You're embedding chat into an existing UI with its own patterns.
- You're building a non-chat surface that still talks to an agent (a dashboard, a canvas, an inspector) and want
useRenderToolCall/useRenderActivityMessageon their own. - You want to render generative UI primitives outside of a chat entirely.
The core hooks#
Three hooks power it, and they're the same ones <CopilotChat> uses internally.
useAgent({ agentId })— exposes the current conversation (messages,isRunning) and the run-state object.useCopilotKit()— returns the runtime handle you callrunAgent({ agent })on.useRenderToolCall()— returns a function that paints any registered tool call inline.
Minimal example#
Start with a hand-rolled message list and composer built from useAgent + useCopilotKit:
ms-agent-harness-dotnet::headless-simple. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.The message list is a plain .map() over agent.messages: user messages render as right-aligned bubbles, assistant messages render streamed text plus inline tool calls via renderToolCall({ toolCall }):
ms-agent-harness-dotnet::headless-simple. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.No <CopilotChat />, no slots. The trade-off: you only get text and tool calls. Reasoning messages, activity messages, and custom before/after slots won't show up unless you wire them in yourself, which is exactly what the complete example covers.
Complete example#
The headless-complete cell rebuilds the full generative-UI composition from the low-level hooks directly, without importing <CopilotChatMessageView>: text, tool calls, reasoning cards, A2UI + MCP Apps activity messages, and custom before/after message slots.
The useRenderedMessages hook#
The cell's central piece is a hand-rolled useRenderedMessages(messages, isRunning) that returns the same flat list of messages, each augmented with a renderedContent: ReactNode field. This hook is a manual recreation of what <CopilotChatMessageView> does:
ms-agent-harness-dotnet::headless-complete. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.Three low-level hooks feed it:
useRenderToolCall()— returns the renderer for any registered tool call (per-tool viauseRenderTool/useComponent, plus the wildcard fromuseDefaultRenderTool).useRenderActivityMessage()— renders A2UI + MCP Apps activity messages for the current agent scope.useRenderCustomMessages()— invokesrenderCustomMessagehooks registered against the activeCopilotChatConfigurationProvider, emitting"before"and"after"slots around every message.
Per-role dispatch#
The role-switch mirrors CopilotChatMessageView's renderMessageBlock exactly: assistant bodies get text and tool calls, user bodies get their text content, reasoning messages go through the <CopilotChatReasoningMessage> leaf, and activity messages route through renderActivityMessage:
ms-agent-harness-dotnet::headless-complete. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.Tool-call composition#
For each toolCall on an assistant message, we look up the sibling tool-role message (keyed by toolCallId) and hand both to renderToolCall:
ms-agent-harness-dotnet::headless-complete. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.Bubble chrome#
The UserBubble and AssistantBubble components are pure chrome: they receive the pre-rendered node from useRenderedMessages and drop it into a styled container. No chat primitives are imported here:
ms-agent-harness-dotnet::headless-complete. Known demos are bundled from manifest demos[i]; check the cell id and framework slug.Next steps#
- Slots — less work than going fully headless, often enough.
- CSS customization — when you just need to re-skin the defaults.
