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

Open .vendo/theme.json

vendo init already wrote it, filled from your app’s own CSS. Five groups, one file.
.vendo/theme.json
2

Adjust the tokens you care about

Edit values in place. Here a purple accent, tighter corners, and a compact scale:
.vendo/theme.json
Keep all five groups. The file is a whole theme, not a patch.
3

Every surface re-themes

The provider you pasted from init already imports the file, so there is nothing else to wire.
app/layout.tsx
Save and reload. The overlay, slots, generated screens, approval modals, and toasts each stamp these tokens on their own root and move together. Nothing in the chrome hardcodes a Vendo brand.

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.

Good to know

  • Extraction reads your root layout, the CSS that layout imports, and tailwind.config.*, taking the shadcn and Tailwind names exactly: --primary, --background, --card, --font-sans, --radius.
  • Every group also lands as --vendo-* CSS variables on the surface root, so var(--vendo-density-card-padding) in your own styles tracks the same theme.
  • --vendo-color-scheme is derived, not written. Vendo takes the relative luminance of colors.background and flips to dark below 0.179, so a dark brand gets dark chrome on its own.
  • Those five groups are the whole required shape. Every token added since is optional, so a theme file written a year ago still parses.
  • vendo sync re-reads your app and updates the slots you have not touched. A slot you edited by hand is pinned and reported, 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.

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 renders at 16px to stop iOS auto-zoom, and icon buttons and the overlay close control expand to 44px.Desktop chrome is unchanged.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.