Skip to content

Commit 52b9b20

Browse files
ataibarkaiclaude
andcommitted
docs(showcase/shell): custom-look-and-feel — CSS first, Slots (Subcomponents), align unselected tree
- Reorder meta.json so CSS is the first sub-page (easiest rung of the customization ladder), then Slots, Headless UI, Reasoning Messages. - Retitle slots.mdx frontmatter to "Slots (Subcomponents)" so the sidebar nav reads the user-facing term people actually search for. - css.mdx: demonstrate the new file+lines Snippet by pulling the user/assistant bubble block straight from the cell's theme.css. - headless-ui.mdx: expand from a bare IntegrationGrid stub into a full page with minimal + complete examples, the three core hooks, and Snippet pulls from both headless-simple and headless-complete cells. - Mirror the css/slots changes into unselected/custom-look-and-feel/ so the framework-agnostic tree stays in step (add css.mdx, reorder meta.json, retitle slots nav). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 384a803 commit 52b9b20

7 files changed

Lines changed: 228 additions & 9 deletions

File tree

showcase/shell/src/content/docs/custom-look-and-feel/css.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ can target specific pieces of the UI from your own stylesheet:
9090
}
9191
```
9292

93+
The demo's `theme.css` wraps every selector under `.chat-css-demo-scope` so
94+
the overrides don't leak out — here's the user/assistant bubble block from
95+
that file:
96+
97+
<Snippet
98+
file="src/app/demos/chat-customization-css/theme.css"
99+
lines="30-57"
100+
title="frontend/src/app/theme.css — user + assistant message bubbles"
101+
/>
102+
93103
### Reference
94104

95105
| CSS Class | Description |
Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,82 @@
11
---
22
title: "Fully Headless UI"
3-
description: "Fully customize your Copilot's UI from the ground up using headless UI"
3+
description: "Build any UI — chat or not — on top of the CopilotKit primitives with zero UI opinions."
44
icon: "lucide/Settings"
55
hideTOC: true
6+
snippet_framework: langgraph-python
7+
snippet_cell: headless-complete
68
---
79

810
## What is this?
911

10-
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, and streaming.
12+
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.
1113

1214
## When should I use this?
1315

14-
Use headless UI when the [slot system](/custom-look-and-feel/slots) isn't enough — for example, when you need a completely different layout, want to embed the chat into an existing UI, or are building a non-chat interface that still communicates with an agent.
16+
Use headless UI when:
1517

16-
## Get started by choosing your AI backend
18+
- The [slot system](/custom-look-and-feel/slots) isn't enough — you need a completely different layout.
19+
- You're embedding chat into an existing UI with its own patterns.
20+
- You're building a **non-chat surface** that still talks to an agent (a dashboard, a canvas, an inspector) and want `useRenderToolCall` / `useRenderActivityMessage` on their own.
21+
- You want to render generative UI primitives outside of a chat entirely.
1722

18-
<IntegrationGrid path="custom-look-and-feel/headless-ui" />
23+
<InlineDemo integration="langgraph-python" demo="headless-complete" />
24+
25+
## The core hooks
26+
27+
Three hooks do the heavy lifting — they're the same primitives `<CopilotChat>` uses internally.
28+
29+
- `useAgent({ agentId })` — exposes the current conversation (`messages`, `isRunning`) and the run-state object.
30+
- `useCopilotKit()` — returns the runtime handle you call `runAgent({ agent })` on.
31+
- `useRenderToolCall()` — returns a function that paints any registered tool call inline.
32+
33+
## Minimal example
34+
35+
Start small — a hand-rolled message list and composer built from `useAgent` + `useCopilotKit`:
36+
37+
<Snippet cell="headless-simple" region="use-agent-simple" title="frontend/src/app/page.tsx — useAgent + useCopilotKit" />
38+
39+
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 })`:
40+
41+
<Snippet cell="headless-simple" region="message-list-simple" title="frontend/src/app/page.tsx — message list" />
42+
43+
No `<CopilotChat />`, no slots. The trade-off: you only get text + 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.
44+
45+
## Complete example
46+
47+
The `headless-complete` cell rebuilds the **full** generative-UI composition — text, tool calls, reasoning cards, A2UI + MCP Apps activity messages, custom before/after message slots — from the low-level hooks directly, without importing `<CopilotChatMessageView>`.
48+
49+
### The `useRenderedMessages` hook
50+
51+
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**:
52+
53+
<Snippet region="use-rendered-messages-hook" title="frontend/src/app/use-rendered-messages.tsx — composition hook" />
54+
55+
Three low-level hooks feed it:
56+
57+
- `useRenderToolCall()` — returns the renderer for any registered tool call (per-tool via `useRenderTool` / `useComponent`, plus the wildcard from `useDefaultRenderTool`).
58+
- `useRenderActivityMessage()` — renders A2UI + MCP Apps activity messages for the current agent scope.
59+
- `useRenderCustomMessages()` — invokes `renderCustomMessage` hooks registered against the active `CopilotChatConfigurationProvider`, emitting `"before"` and `"after"` slots around every message.
60+
61+
### Per-role dispatch
62+
63+
The role-switch mirrors `CopilotChatMessageView`'s `renderMessageBlock` exactly — assistant bodies get text + tool calls, user bodies get their text content, reasoning messages go through the `<CopilotChatReasoningMessage>` leaf, and activity messages route through `renderActivityMessage`:
64+
65+
<Snippet region="manual-activity-message-rendering" title="frontend/src/app/use-rendered-messages.tsx — per-role dispatch" />
66+
67+
### Tool-call composition
68+
69+
For each `toolCall` on an assistant message, we look up the sibling `tool`-role message (keyed by `toolCallId`) and hand both to `renderToolCall`:
70+
71+
<Snippet region="manual-tool-call-rendering" title="frontend/src/app/use-rendered-messages.tsx — assistant + tool calls" />
72+
73+
### Bubble chrome
74+
75+
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:
76+
77+
<Snippet region="custom-bubbles" title="frontend/src/app/{user,assistant}-bubble.tsx — pure chrome" />
78+
79+
## Next steps
80+
81+
- [Slots](/custom-look-and-feel/slots) — less work than going fully headless, often enough.
82+
- [CSS customization](/custom-look-and-feel/css) — when you just need to re-skin the defaults.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"title": "Custom Look and Feel",
33
"icon": "lucide/LayoutDashboard",
4-
"pages": ["slots", "css", "headless-ui"]
4+
"pages": ["css", "slots", "headless-ui", "reasoning-messages"]
55
}

showcase/shell/src/content/docs/custom-look-and-feel/slots.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "Slots"
2+
title: "Slots (Subcomponents)"
33
icon: "lucide/Brush"
44
description: "Customize any part of the chat UI by overriding individual sub-components via slots."
55
hideTOC: true
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: "CSS Customization"
3+
icon: "lucide/Palette"
4+
description: "Theme CopilotKit components via CSS variables and class overrides — the quickest path to a custom look."
5+
hideTOC: true
6+
---
7+
8+
## What is this?
9+
10+
CopilotKit's chat UI components can be re-skinned purely via CSS — no component overrides, no headless rewrites. This is the **fastest** rung on the customization ladder: keep every built-in feature, change every color.
11+
12+
You can:
13+
14+
- Override CopilotKit CSS variables (`--copilot-kit-primary-color`, etc.) to re-tint the whole UI
15+
- Target the built-in `.copilotKit...` class names for structural tweaks
16+
- Swap fonts per surface (messages, input, bubbles)
17+
- Replace icons and labels via component props
18+
19+
If you need to change **behavior** — not just look — see
20+
[slots](/custom-look-and-feel/slots) or
21+
[fully headless UI](/custom-look-and-feel/headless-ui).
22+
23+
## CSS variables (easiest)
24+
25+
The easiest way to change the colors used in the Copilot UI components is to
26+
override CopilotKit CSS variables on a wrapper element around your chat:
27+
28+
```tsx title="page.tsx"
29+
import { CopilotKitCSSProperties } from "@copilotkit/react-core/v2";
30+
31+
<div
32+
style={
33+
{
34+
"--copilot-kit-primary-color": "#222222",
35+
} as CopilotKitCSSProperties
36+
}
37+
>
38+
<CopilotSidebar />
39+
</div>
40+
```
41+
42+
Or apply them from a scoped stylesheet:
43+
44+
```css title="theme.css"
45+
.chat-css-demo-scope {
46+
--copilot-kit-primary-color: #ff006e;
47+
--copilot-kit-contrast-color: #ffffff;
48+
--copilot-kit-background-color: #fff5f9;
49+
}
50+
```
51+
52+
### Reference
53+
54+
| CSS Variable | Description |
55+
|-------------|-------------|
56+
| `--copilot-kit-primary-color` | Main brand/action color — used for buttons, interactive elements |
57+
| `--copilot-kit-contrast-color` | Color that contrasts with primary — used for text on primary elements |
58+
| `--copilot-kit-background-color` | Main page/container background color |
59+
| `--copilot-kit-secondary-color` | Secondary background — used for cards, panels, elevated surfaces |
60+
| `--copilot-kit-secondary-contrast-color` | Primary text color for main content |
61+
| `--copilot-kit-separator-color` | Border color for dividers and containers |
62+
| `--copilot-kit-muted-color` | Muted color for disabled/inactive states |
63+
64+
## Custom CSS
65+
66+
In addition to customizing the colors, the CopilotKit CSS is structured to
67+
allow customization via CSS classes. Target specific pieces of the UI from
68+
your own stylesheet:
69+
70+
```css title="globals.css"
71+
.copilotKitButton {
72+
border-radius: 0;
73+
}
74+
75+
.copilotKitMessages {
76+
padding: 2rem;
77+
}
78+
79+
.copilotKitUserMessage {
80+
background: #007AFF;
81+
}
82+
```
83+
84+
### Reference
85+
86+
| CSS Class | Description |
87+
|-----------|-------------|
88+
| `.copilotKitMessages` | Main container for all chat messages |
89+
| `.copilotKitInput` | Text input container with typing area and send button |
90+
| `.copilotKitUserMessage` | Styling for user messages |
91+
| `.copilotKitAssistantMessage` | Styling for AI responses |
92+
| `.copilotKitHeader` | Top bar of chat window containing title and controls |
93+
| `.copilotKitButton` | Primary chat toggle button |
94+
| `.copilotKitWindow` | Root container defining overall chat window dimensions |
95+
| `.copilotKitMarkdown` | Styles for rendered markdown content |
96+
| `.copilotKitCodeBlock` | Code snippet container with syntax highlighting |
97+
| `.copilotKitSidebar` | Styles for sidebar chat mode |
98+
| `.copilotKitPopup` | Styles for popup chat mode |
99+
100+
## Custom fonts
101+
102+
Customize the fonts by updating the `fontFamily` property on the
103+
relevant CopilotKit classes:
104+
105+
```css title="globals.css"
106+
.copilotKitMessages {
107+
font-family: "Arial, sans-serif";
108+
}
109+
110+
.copilotKitInput {
111+
font-family: "Arial, sans-serif";
112+
}
113+
```
114+
115+
## Custom icons
116+
117+
Customize icons by passing the `icons` prop to `CopilotSidebar`, `CopilotPopup`,
118+
or `CopilotChat`:
119+
120+
```tsx
121+
<CopilotChat
122+
icons={{
123+
openIcon: <YourOpenIconComponent />,
124+
closeIcon: <YourCloseIconComponent />,
125+
}}
126+
/>
127+
```
128+
129+
## Custom labels
130+
131+
Customize all user-facing copy via the `labels` prop:
132+
133+
```tsx
134+
<CopilotChat
135+
labels={{
136+
welcomeMessageText: "Hello! How can I help you today?",
137+
modalHeaderTitle: "My Copilot",
138+
chatInputPlaceholder: "Ask me anything!",
139+
}}
140+
/>
141+
```
142+
143+
## Get started by choosing your AI backend
144+
145+
<IntegrationGrid path="custom-look-and-feel/css" />
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"title": "Custom Look and Feel",
33
"icon": "lucide/LayoutDashboard",
4-
"pages": ["slots", "headless-ui"]
4+
"pages": ["css", "slots", "headless-ui"]
55
}

showcase/shell/src/content/docs/unselected/custom-look-and-feel/slots.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Slots
2+
title: Slots (Subcomponents)
33
icon: "lucide/Brush"
44
description: Customize any part of the chat UI by overriding individual sub-components via slots.
55
hideTOC: true

0 commit comments

Comments
 (0)