Notes

How forms work

A form is the same idea: register a DynamicForm component, then have the agent return a field list in props.

componentRegistry.register("DynamicForm", DynamicForm);
System prompt: When you need to collect details, respond with this JSON: { "component": "DynamicForm", "props": { "title": "Schedule a demo", "fields": [ { "label": "Email", "type": "email", "required": true }, { "label": "Notes", "type": "textarea" } ], "submit_text": "Request meeting" } }

The form renderer turns each field object into inputs, handles validation/submission, and can use formStyles or props.styles for the visual variants above.

How components work

1. Create a normal component.

function ProductCard({ title }) { const button = document.createElement("button"); button.textContent = "Add to Cart"; button.addEventListener("click", () => { button.textContent = "Added"; }); return button; }

2. Register it, then initialize Persona. The widget reads from the shared registry automatically.

import { componentRegistry, initAgentWidget } from "@runtypelabs/persona"; componentRegistry.register("ProductCard", ProductCard); initAgentWidget({ target: "#app", config: { apiUrl: "/api/chat/dispatch-component", parserType: "json" // needed for streamed JSON directives } });

3. Tell the agent to return this JSON when it should show the component. This usually goes in your system prompt or flow instructions.

System prompt: When showing a product, respond with this JSON: { "component": "ProductCard", "props": { "title": "{{name of the product}}", "price": {{price of the product}} } }

4. Persona renders it in the chat.

// The asks for a product. // LLM returns the JSON, Persona renders registered component. // The user clicks "Add to Cart". // Action performed, button changes to "Added".

The component is normal DOM, so buttons, forms, saved state, and page events all work.