THE VANILLAJS
Agent UI Library

Persona helps you create agentic front-end experiences for the web, in pure JS. Lightweight, extensible, and WebMCP-native.

$npm install @runtypelabs/persona
Runs on any stack
+ any SSE backend
Demos that make you [and your browser] happy
Start in a few lines

Floating, docked, or fullscreen UX

Pick your jump-off point and customize from there: a launcher floating in the corner, a copilot docked beside your app, or a fullscreen assistant. You move between them by changing the launcher config, not your agent or app.

Install via

A launcher in the corner that opens a floating panel. The drop-in default for support, docs, sales, or onboarding. No layout config required.

import "@runtypelabs/persona/widget.css";
import { initAgentWidget } from "@runtypelabs/persona";

initAgentWidget({
  target: "#chat",
  config: {
    apiUrl: "https://your-api.com/chat",
    // floating launcher is the default
  },
});
Agentic

Operates Your App

Expose page actions, search, carts, bookings, forms, as WebMCP tools and the agent drives them directly, with user approval built in. No backend integration required.

Isolation

Won't Break Your Styles

Shadow DOM rendering and prefixed CSS keep widget and host styles fully separate. Drop it into any page: nothing leaks in, nothing leaks out.

Transport

Streams From Any Backend

SSE streaming with pluggable parsers. Adapt any request or event shape with customFetch and parseSSEEvent: Runtype is the fast path, not a requirement.

Theming

Style It Like It Belongs

A three-layer token tree, palette, semantic, component, with dark mode and a live theme editor. Match your brand without forking the widget.

Quick Start

Choose Your Integration Path

Start from a pre-built example for your stack, or ship a hosted widget with Runtype.

Start from a pre-built example. Each one mounts the real widget against a working backend you can clone, read, and adapt.
Or wire your own SSE backend by hand
01

Install Persona

Add the widget package to your app.

02

Mount the widget

Import the stylesheet, initialize Persona, and point it at your SSE endpoint. Adapt any request or event shape with customFetch and parseSSEEvent.

import "@runtypelabs/persona/widget.css";
import { initAgentWidget } from "@runtypelabs/persona";

initAgentWidget({
  target: "#chat",
  config: { apiUrl: "https://your-api.com/chat" },
});

Built an adapter for your stack? Contribute it back.

// Your page registers a tool…
document.modelContext.registerTool({
  name: "search_products",
  description: "Search the catalog.",
  inputSchema: {
    type: "object",
    properties: {
      query: { type: "string" },
    },
    required: ["query"],
  },
  async execute({ query }) {
    return searchCatalog(query);
  },
});

// Persona discovers it through WebMCP
// and asks before calling it.
WebMCP Standard

Let Agents Use The Tools Already On Your Page

WebMCP gives the browser a standard place for page tools: document.modelContext. Persona discovers those tools, asks the user before calling them, and streams the result back into the conversation. Search, carts, bookings, and forms stay in your page code. The chat UI just becomes another way to use them.

Demos & Patterns

See What Persona Can Do

Agentic, layout, voice, and business scenarios: every demo is a working page you can open and inspect.

persona init --help
Usage
  $ npx @runtypelabs/cli@latest persona init [options]

Description
  Create agent + client token and output a Persona embed snippet.

Options
  --agent-name <name>         Name for the new agent
  --agent-description <text>  Optional agent description
  --token-name <name>         Custom name for the client token
  --environment <env>         test or live (default: test)
  --origins <origins...>     Allowed origins (default: *)
  --format <fmt>             script-installer | script-manual | esm | react
                              (default: script-installer)
  --print-snippet             Print the snippet to stdout
  --target-selector <sel>    Widget mount target (default: body)
  --api-url <url>            Override API base URL
  --json                      Structured JSON output (includes snippet)
  --yes                       Accept defaults without prompting
  --tty / --no-tty            Force interactive or non-interactive mode

Examples
  # Interactive, just run it
  $ npx @runtypelabs/cli@latest persona init

  # Non-interactive for CI
  $ RUNTYPE_API_KEY=sk_... npx @runtypelabs/cli@latest persona init \
      --agent-name "Support" --no-tty --json

  # ESM snippet for a bundled app
  $ npx @runtypelabs/cli@latest persona init --format esm