Skip to main content
Your brand, extracted from your own UI on day one, then yours to edit.

The picture

The same Vendo overlay, in two products. Same markup, same components, different token file. Nothing in the chrome hardcodes a Vendo brand. A dark background flips the whole surface on its own. That last row is derived, not written. Vendo takes the relative luminance of colors.background, flips to dark below 0.179, and the chrome’s light-dark() branches follow.

The token file

Five groups, one file. Every shipped surface and every prewired primitive reads them.
.vendo/theme.json
Those five groups are the whole required shape. Every token added since is optional, so a theme file written a year ago still parses today. These land as --vendo-* CSS variables on the surface root. Read them from your own styles and a custom panel tracks the same theme:

Where it comes from

You do not write it from scratch. You inherit it, then change what you want. vendo init reads your app’s own CSS and writes the file. It then prints the paste that imports it into your provider:
app/layout.tsx
The prop takes a partial theme and merges it group by group over Vendo’s neutral default. Pass the file, an inline object, or the file with a few keys spread on top. Later runs of vendo sync re-read your app and update the slots you have not touched. A slot you edited by hand is pinned and reported rather than overwritten, and vendo sync --theme-refresh takes your app’s value back.
createVendo has its own theme key, resolved once at boot. It feeds app generation and the one theme line in the agent’s prompt, not the chrome. Leave it unset and the server reads .vendo/theme.json.

One surface, a different theme

The provider sets the brand for everything under it. A single surface can differ — a dark assistant panel on a light page, a compact slot in a dense console — with its own theme prop.
It merges over the provider’s resolved theme group by group, the same way the provider’s merges over Vendo’s default — so a surface states only what differs, and the groups it leaves out stay the brand. With no provider above it, the merge runs over the default. Six surfaces take it: VendoOverlay, VendoSlot, VendoTrigger, VendoAppEmbed, VendoApprovalEmbed, and VendoToolResult. Type it with Partial<VendoTheme>. What a surface sends to document.body goes with it. The overlay panel, the approval modal a press parks on, and the toast stack all wear the theme of the surface they came from rather than falling back to the provider’s.
A surface theme styles Vendo’s frame, not the view inside it. A generated view keeps the provider theme either way it mounts — served in an iframe, or rendered natively as a pin — because the view is its own theme boundary and restates the provider’s tokens on itself. Theme a surface to change the chrome around a generated view; change the provider to change the view.
A slot showing your own markup renders it untouched, with no Vendo wrapper at all. There is no chrome on screen there, so theme has nothing to style.

Where to go next

Theme is how it looks. These three are what it knows.

Instructions

The prose your agent reads before every turn.

Context

Who is asking, and what their screen shows.

Knowledge

Answers from your docs, with citations.

Token reference

Every value the five groups expand into. Open a panel when you need an exact number.
density: "compact" tightens control sizes, padding, and gaps, and comfortable relaxes them. Every compact value is less than or equal to its comfortable counterpart.A Kit container takes its own density adjective and re-emits this same ladder on its own element. That is how a compact table sits inside a comfortable page.
Vendo derives color-scheme from the WCAG relative luminance of colors.background and publishes it as --vendo-color-scheme. Backgrounds below 0.179 resolve to dark, and everything else resolves to light.A color the parser cannot read resolves to light. Only #rgb, #rgba, #rrggbb, and #rrggbbaa are parsed.The chrome’s light-dark() branches key off this variable, so a dark-brand host gets dark chrome with no extra configuration. Read it from your own styles to make a custom surface track the same flip:
motion: "reduced" collapses the duration to 0ms. The easing curve does not change.Both defaults are overridable. Set motionDuration or motionEasing on the theme and full motion uses your values instead, while reduced still pins the duration to 0ms.
The overlay panel goes full-bleed at viewports 767px wide and narrower. It covers the host layout rather than docking inside a squeezed column.The panel portals to document.body, so a transformed or filtered host ancestor cannot confine it. It pads all four edges with the iOS safe-area insets.Touch ergonomics are keyed to small viewports or coarse pointers, so a tablet in a wide orientation still gets honest targets. The composer and the palette search render at 16px to stop iOS auto-zoom, and icon buttons and the overlay close control expand to 44px.Desktop chrome is unchanged, and the palette keeps its own presentation at every width.While the takeover is open, the panel stamps a --fl-kb-inset variable derived from visualViewport so the composer lifts above the on-screen keyboard. Read it if you render a custom surface inside one:
Keep the 0px fallback. Outside the takeover the variable is not set at all, and with the takeover open but no keyboard showing it is 0px.