Design the embed visually in the dashboard and have it apply automatically, or export the props and pin them in your code.
The appearance editor in the dashboard is the recommended way to customize the launcher, theme, typography, composer, and start screen against a live preview. Changes are saved with the project and applied at runtime, so restyling a deployed agent does not require a release.
You can also pass any of it as props instead. Props always win, so the two approaches compose rather than compete.
The embed resolves each setting in this order, so the last one that defines a value wins:
HeroUIAgent — anything you write in code.Because the merge happens field by field, a partial override stays partial. Pinning your brand accent in code keeps the launcher icon, radius, greeting, and font that the dashboard supplies:
<HeroUIAgent
getAuthToken={getAuthToken}
agentId={process.env.NEXT_PUBLIC_HEROUI_AGENT_ID!}
// Everything else still comes from the dashboard.
appearance={{theme: {colors: {accent: "#7c3aed"}}}}
/>When appearance needs to ship with your application, disable the saved dashboard configuration and pass the experience as props:
<HeroUIAgent
getAuthToken={getAuthToken}
agentId={process.env.NEXT_PUBLIC_HEROUI_AGENT_ID!}
remoteConfig={false}
appearance={{
viewMode: "floating",
launcher: {position: "bottom-right"},
theme: {
colorScheme: "system",
colors: {
accent: {light: "#7c3aed", dark: "#a78bfa"},
},
radius: "round",
typography: {
baseSize: 15,
fontFamily: "Inter, sans-serif",
},
},
}}
composer={{placeholder: "Ask about your workspace..."}}
startScreen={{
greeting: "How can I help?",
prompts: ["Summarize recent activity", "Show my open tasks"],
}}
/>The Setup panel generates this shape for you when you turn on Inline configuration. Use it when appearance changes should go through code review and deploy with the application.
| Dashboard | Inline props | |
|---|---|---|
| Changing a color | Takes effect on the next page load | Requires a deploy |
| Reviewed in pull requests | No | Yes |
| Who can change it | Anyone with dashboard access | Anyone who can ship code |
| Differs per environment | Use a separate project | Use your own config |
Most teams start with the dashboard and pin individual values in code as they become load-bearing.
Changes save automatically as you edit. Once saved, they reach visitors on their next page load — within about a minute, since the configuration is cached at the edge.
The Preview section switches the mock conversation between empty, streaming, generated components, sources, activity, approval, and error states. Design against the states your product will actually produce, not just the empty one.
Work through Launcher, Colors, Typography, and Style. Each control updates the preview immediately.
Open Code in the toolbar. The Setup panel generates a complete embed for Next.js, Vite, TanStack Start, or React Router.
By default the snippet is minimal — identity, the token exchange, and your client tools — because everything you designed here is read at runtime. Turn on Inline configuration to write the whole design into the snippet instead, which also sets remoteConfig={false} so the two never disagree.
Copy the snippet into your application. You only need to deploy again if you switch to inline configuration and change the design.
Every editor control maps to a prop on HeroUIAgent — the same name you would use to override it in code. The reference documentation for each group is linked in the last column.
| Editor section | Controls | Prop |
|---|---|---|
| Preview | Chat state, view mode, Beta badge | appearance.viewMode, showBetaBadge |
| Launcher | Position, custom icon, icon background, custom CSS | appearance.launcher |
| Colors | Accent, background, foreground, surface, secondary surface, overlay, tooltip | appearance.theme.colors |
| Typography | Font family and base size from 12 to 18 pixels | appearance.theme.typography |
| Style | Surface style and radius | appearance.theme.radius; surface style is project-only |
| Streaming | Caret style and text animation | markdown |
| Start screen | Greeting, suggested prompts, prompt shortcuts | startScreen |
| Composer | Placeholder, disclaimer, attachments, dictation, permissions, model picker | composer, permissions |
| Tools | Built-in toolkit switches | See Tools |
| Response actions | Copy, feedback, and retry buttons | responseActions |
Accent colors apply to controls, generated UI, focus states, and the launcher unless you give the
launcher its own background. User message bubbles stay neutral: they use the Agent's surface and
foreground tokens (--ha-surface and --ha-fg), not the accent.
Two sections behave differently from the rest, and both are easy to trip over.
Tools is not appearance. The toolkit switches here are the same ones on the Tools page — they are stored with the project and take effect server-side, without a deploy. Web, image, and news search also surface as capabilities props in the generated code.
Surface style is stored with the project. Unlike the rest of the theme, the surface variant is read by the runtime, because it decides how generated components are surfaced in a response.
| Mode | Behavior |
|---|---|
| Floating | A launcher button in a bottom corner opens a panel above your page |
| Sidebar | A full-height panel docked to the right edge |
Launcher options only apply in floating mode, and the editor hides them in sidebar mode for that reason. Sidebar suits products where the agent is a primary surface; floating suits products where it is an assistant.
On desktop, opening a floating panel does not lock the host page. Visitors can keep scrolling and
interacting with the page behind it. By default, clicking or tapping outside the panel closes it.
Set appearance.shouldCloseOnInteractOutside to false or () => false when visitors need to keep
the chat open while selecting items on the page — for example, when a client tool tracks table rows or
canvas objects. A predicate can filter by element. Escape still closes the panel. The full-screen
mobile panel is modal and locks background scroll while open.
Choose theme variants from the Design system picker. Its Pro collection includes Brutalism, Glass, and Mouve alongside the prebuilt and saved design systems.
A variant ships as its own stylesheet from @heroui-pro/react, so selecting one adds a package and a second import alongside the base styles:
npm install @heroui-pro/reactimport "@heroui/agent/css";
import "@heroui-pro/react/agent-themes/brutalism";The stylesheet is prescoped to the agent root, so it restyles the panel without touching the rest of your page. Only import the one variant you selected — each sheet styles the agent surface directly, so loading several makes the last one win.
This import is required even with the minimal snippet. A stylesheet is not something the embed can fetch on your behalf, so switching variants in the dashboard is the one appearance change that also needs a code change.
Pro variants and saved design systems require an active Pro license, which is what grants access
to the @heroui-pro/react package. The editor shows an upgrade prompt rather than failing
silently.
The Design system picker applies a prebuilt variant or seeds colors, typography, radius, and theme variant from a saved HeroUI Design System, so the embed inherits your product's tokens instead of being styled twice.
The customizer preview updates immediately when you choose a different design system, including its accent, typography, radius, and theme variant.
The seeded values are a snapshot. Editing any color, font, or radius by hand clears the link to the design system — the embed keeps your manual values rather than silently drifting back.
Choosing a non-default typeface makes the embed load that webfont into your page, so a font selected here renders without any work on your side.
The exception is inline configuration: that snippet carries the webfont itself, either as a stylesheet <link> or an @font-face rule. Keep that markup when you paste it, or the family name resolves to a fallback and the embed looks unstyled. The same applies if you set appearance.theme.typography.fontFamily in code — naming a family does not load it.
Reset — or pressing R — returns every appearance value to its default after a confirmation. It does not touch your system prompt, knowledge base, tools, or API keys.