> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vendo.run/llms.txt
> Use this file to discover all available pages before exploring further.

# UI components

> The shipped Vendo chrome — overlay, palette, thread, approvals, activities, slot, page, and voice surfaces — playable inline, plus a full playground with a live theme editor.

export const VendoLauncher = () => {
  useEffect(() => {
    let dispose = null;
    let cancelled = false;
    const mountIt = () => {
      if (cancelled || dispose || !window.VendoDocsEmbed) return;
      try {
        dispose = window.VendoDocsEmbed.mountLauncher();
      } catch (error) {
        console.error("[vendo-embed]", error);
      }
    };
    if (window.__vendoDocsEmbedFailed) return;
    if (window.VendoDocsEmbed) {
      mountIt();
    } else {
      window.addEventListener("vendo-docs-embed-ready", mountIt, {
        once: true
      });
      const existing = document.querySelector('script[data-vendo-docs-embed]');
      if (!existing) {
        const script = document.createElement("script");
        script.src = "https://vendo.run/playground/embed.js";
        script.async = true;
        script.dataset.vendoDocsEmbed = "";
        script.onerror = () => {
          window.__vendoDocsEmbedFailed = true;
          window.dispatchEvent(new Event("vendo-docs-embed-error"));
        };
        document.head.appendChild(script);
      }
      if (window.VendoDocsEmbed) mountIt();
    }
    return () => {
      cancelled = true;
      window.removeEventListener("vendo-docs-embed-ready", mountIt);
      if (dispose) dispose();
    };
  }, []);
  return null;
};

export const VendoEmbed = ({surface, poster, alt, height = 480, caption}) => {
  const containerRef = useRef(null);
  const disposeRef = useRef(null);
  const [live, setLive] = useState(false);
  const [failed, setFailed] = useState(false);
  useEffect(() => {
    let cancelled = false;
    const mount = () => {
      if (cancelled || disposeRef.current || !containerRef.current) return;
      try {
        disposeRef.current = window.VendoDocsEmbed.mount(containerRef.current, {
          scenario: surface
        });
        setLive(true);
      } catch (error) {
        console.error("[vendo-embed]", error);
        setFailed(true);
      }
    };
    const onReady = () => mount();
    const onError = () => {
      if (!cancelled) setFailed(true);
    };
    if (window.__vendoDocsEmbedFailed) {
      setFailed(true);
      return;
    }
    if (window.VendoDocsEmbed) {
      mount();
    } else {
      window.addEventListener("vendo-docs-embed-ready", onReady, {
        once: true
      });
      window.addEventListener("vendo-docs-embed-error", onError, {
        once: true
      });
      const existing = document.querySelector('script[data-vendo-docs-embed]');
      if (!existing) {
        const script = document.createElement("script");
        script.src = "https://vendo.run/playground/embed.js";
        script.async = true;
        script.dataset.vendoDocsEmbed = "";
        script.onerror = () => {
          window.__vendoDocsEmbedFailed = true;
          window.dispatchEvent(new Event("vendo-docs-embed-error"));
        };
        document.head.appendChild(script);
      }
      if (window.VendoDocsEmbed) mount();
    }
    return () => {
      cancelled = true;
      window.removeEventListener("vendo-docs-embed-ready", onReady);
      window.removeEventListener("vendo-docs-embed-error", onError);
      if (disposeRef.current) {
        disposeRef.current();
        disposeRef.current = null;
      }
    };
  }, [surface]);
  return <figure style={{
    margin: "1.5rem 0"
  }}>
      <div style={{
    position: "relative",
    border: "1px solid #e5e5e5",
    borderRadius: "12px",
    overflow: "hidden",
    minHeight: `${height}px`
  }}>
        {!live && <img src={poster} alt={alt} style={{
    display: "block",
    width: "100%",
    margin: 0
  }} />}
        {!live && !failed && <div style={{
    position: "absolute",
    right: 10,
    bottom: 10,
    padding: "4px 10px",
    borderRadius: "999px",
    background: "rgba(17,17,17,.78)",
    color: "#fff",
    fontSize: "11px",
    fontWeight: 600
  }}>
            loading live component…
          </div>}
        {failed && <div style={{
    position: "absolute",
    right: 10,
    bottom: 10,
    padding: "4px 10px",
    borderRadius: "999px",
    background: "rgba(17,17,17,.78)",
    color: "#fff",
    fontSize: "11px",
    fontWeight: 600
  }}>
            live embed unavailable — <a href="https://vendo.run/playground" style={{
    color: "#fff",
    textDecoration: "underline"
  }}>open the playground</a>
          </div>}
        <div ref={containerRef} style={{
    display: live ? "block" : "none",
    minHeight: `${height}px`,
    padding: "16px"
  }} />
      </div>
      {caption && <figcaption style={{
    fontSize: "0.8rem",
    color: "#6b6b76",
    textAlign: "center",
    marginTop: "0.5rem"
  }}>
          {caption}{live ? " — this is the real component; click around." : ""}
        </figcaption>}
    </figure>;
};

Vendo ships a complete set of brand-native chrome components. Every surface
derives its colors, typography, radius, density, and motion from your
[VendoTheme tokens](/connect/theming), so the same components render on-brand
in any host product. **The components below are live** — the real chrome
mounted against scripted data, the same components your hosts mount. Type,
click, approve.

## Overlay

The default drop-in: a themed launcher pill that opens a floating conversation
overlay over your product. On phones it becomes an Intercom-style full-bleed
takeover with safe-area and keyboard handling. One component, zero layout
changes to your app.

You don't need a demo for this one — **it's running on this page**. The Vendo
pill in the corner is the shipped component; click it and the overlay opens
over these docs, compact first, growing on your first message.

<VendoLauncher />

<Frame caption="The closed overlay: a quiet launcher pill in the corner, plus the slot invitation.">
  <img src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/hero-launcher.png?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=b40441c5da90179747c6912ebf091bc4" alt="Vendo overlay launcher pill in the corner of a host page" width="2400" height="840" data-path="images/ui/hero-launcher.png" />
</Frame>

## Command palette

The same surface is one keystroke away: **⌘K** opens the conversation overlay
in its compact palette form, with host-registered commands on the chip strip.
If your product already owns ⌘K in a focused input, Vendo stays out of the
way.

<Frame caption="⌘K opens the compact overlay: greeting, suggestions, commands, and the composer.">
  <img src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/hero-palette.png?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=3df9a6bae1af6d3d409b89113a433d08" alt="Vendo command palette — the compact conversation overlay" width="2400" height="1520" data-path="images/ui/hero-palette.png" />
</Frame>

## Thread

The bare conversation surface — composer, streaming replies, tool beats,
approval cards, connect cards, and generated views arriving inline. Use it
when you want the conversation embedded in your own layout instead of an
overlay.

<VendoEmbed surface="thread-streaming" poster="/images/ui/hero-thread-streaming.png" alt="Vendo thread surface with a streaming reply" height={560} caption="A reply streams into the thread: tool beat, live text, stop affordance" />

## Generated views

When the agent builds UI, the view streams into the transcript as it forms —
skeleton first, then live pieces — and lands as an interactive component, not
a screenshot.

<video autoPlay muted loop playsInline type="video/mp4" src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/loop-view-arrival.mp4?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=789ef4f32633c9ebdb34f1d36151fa8b" data-path="images/ui/loop-view-arrival.mp4" />

<VendoEmbed surface="thread-view" poster="/images/ui/hero-generated-view.png" alt="A generated view rendered inline in the Vendo thread" height={620} caption="Ask for a view and watch it form — the finished Renewals radar is interactive" />

## Approvals

A write action parks the turn on an in-thread approval card showing the exact
request. Approve it and the same turn resumes — the card morphs into a toast,
the tool runs, and the agent confirms.

<VendoEmbed surface="approval-flow" poster="/images/ui/hero-approval.png" alt="Vendo approval card pausing a turn on a Slack post" height={560} caption="Pending → approved → resumed: press Approve and the turn continues" />

<video autoPlay muted loop playsInline type="video/mp4" src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/loop-approval-toast.mp4?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=6870c809a07bc00b012acce25498b48a" data-path="images/ui/loop-approval-toast.mp4" />

## Activities

The approvals queue and humanized activity feed as a host-placeable piece:
pending approvals render as actionable cards on top, recent agent activity
below. Drop it into your own settings or admin pages.

<VendoEmbed surface="activities" poster="/images/ui/hero-activities.png" alt="Vendo activities surface with an approval card and activity feed" height={520} caption="VendoActivities: the waiting-on-you queue above the humanized activity feed" />

## Slot

A placeholder in your product that users can fill with their own generated
view. Empty, it shows a quiet ghost with a "Design a view" call to action;
filled, it renders the pinned view; broken, it falls back to your original
markup — never a blank hole.

<VendoEmbed surface="slot-filled" poster="/images/ui/hero-slot-filled.png" alt="Vendo slot holding a pinned generated view" height={460} caption="A filled slot: the user's pinned Renewals radar mounted in the host page" />

## Page

The full workspace console: conversation history, the waiting-on-you approval
strip, apps, automations, connected accounts, and activity in one routed
surface. Mount it on a dedicated route like `/assistant`.

<VendoEmbed surface="page" poster="/images/ui/hero-page.png" alt="Vendo workspace console page" height={640} caption="VendoPage: conversations, the approval strip, and a generated view in one console" />

## Voice

The voice stage: a live orb, streaming captions, views arriving mid-session,
and voice-native approvals — say "approve" or tap. See
[Voice](/capabilities/voice) for wiring it up.

<Frame caption="The voice stage mid-session: captions, a ready view, and a spoken approval.">
  <img src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/hero-voice-stage.png?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=91977ff2a9bd7458c3c770d429e8ddd4" alt="Vendo voice stage with captions and a voice approval" width="2092" height="1172" data-path="images/ui/hero-voice-stage.png" />
</Frame>

<video autoPlay muted loop playsInline type="video/mp4" src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/loop-voice-stage.mp4?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=32803b3e227213693b50de934acb3ef8" data-path="images/ui/loop-voice-stage.mp4" />

## Embeds for your own agent's chat

Three components render Vendo inside a chat surface that is not Vendo's,
when your product keeps its own agent loop and spreads in the
[guarded tool pack](/existing-agents). All three live inside `VendoProvider`
pointed at the wire and take no config props of their own.

* `<VendoToolResult output>` is the dispatcher: give it any `vendo_*` tool
  output and it renders the right embed, or nothing for plain data.
* `<VendoAppEmbed refValue>` renders an inline generated app from a
  `vendo/app-ref@1` envelope: a build beat while the build streams, then the
  live app. In-app interactions go over the wire, not through your loop.
* `<VendoApprovalEmbed refValue>` renders the approve/deny card for a
  `vendo/approval-ref@1` envelope and resolves in place to the executed
  result, "declined", or "expired".

See [Use with your existing agent](/existing-agents) for the envelope
contract and the AI SDK and Mastra walkthroughs.

## Theming

Every component above re-themes from one token file. Extract it with
`vendo init`, or hand-edit `.vendo/theme.json` — the playground's theme editor
emits a valid file from whatever look you land on. See
[Theming](/connect/theming) for the full token reference.

<Frame caption="The playground's theme editor: presets, per-token colors, any Google Font, radius, density, motion.">
  <img src="https://mintcdn.com/vendo/EfUJqmB0F3mRjPNA/images/ui/hero-theme-editor.png?fit=max&auto=format&n=EfUJqmB0F3mRjPNA&q=85&s=6c1d52d01490c623daff6188a5533bad" alt="The Vendo playground theme editor open over the activities surface" width="2400" height="1520" data-path="images/ui/hero-theme-editor.png" />
</Frame>

## Try every surface live

The hosted playground renders each surface against scripted data — no model
key, no database, nothing to install. Use the **Theme** button inside it to
edit every theme token live, copy a ready `.vendo/theme.json`, and share a
themed link.

<iframe src="https://vendo.run/playground" title="Vendo playground — every surface, scripted data, no model key" width="100%" height="720" style={{ border: "1px solid #e5e5e5", borderRadius: "12px" }} />

If the embeds on this page do not load, open
[vendo.run/playground](https://vendo.run/playground) directly, or run it
locally with `npx vendoai playground`.
