Skip to content

Custom Web Components

Pika enables agents to render sophisticated, interactive user interfaces through custom web components. Build components once, deploy them independently, and let agents incorporate them into responses dynamically based on user needs.

Custom web components provide agents with the ability to render rich, interactive UI that goes far beyond simple text responses:

Multi-Context Rendering

Components adapt automatically to different display contexts: inline within messages, dialog overlays, spotlight focus views, and full canvas experiences.

AI-Powered Interaction

Components can invoke the agent directly, requesting structured data or triggering actions, creating truly dynamic user experiences.

Streaming Support

Handle real-time data streams elegantly with built-in support for pending, completed, and error states during content delivery.

Traditional chat applications limit agents to text output. Pika breaks this constraint by allowing agents to render:

  • Data Visualizations: Charts, graphs, and interactive dashboards
  • Interactive Forms: User input collection with validation
  • Rich Media: Image galleries, video players, document viewers
  • Business Widgets: Product cards, order status, appointment schedulers
  • Custom Tools: Calculators, configurators, comparison tables

Agents decide when and how to render components based on user needs:

User: "Show me sales trends for Q4"
Agent: Renders an interactive chart component

User: "Compare our top 3 products"
Agent: Renders a comparison table component

User: "Help me calculate my loan payment"
Agent: Renders a loan calculator component

The agent chooses the appropriate UI dynamically rather than forcing users through predetermined

forms.

Pika supports multiple component deployment models:

Platform-provided components available out of the box:

  • Chart: Powered by Chart.js for data visualization
  • Image: Display images with captions and alt text
  • Prompt: Clickable suggested follow-up questions
  • Download: File download buttons with S3 integration

Svelte components compiled into your application:

  • Fastest performance (no network loading)
  • Full access to application state
  • Ideal for frequently-used components
  • Require application redeployment to update

Standalone components loaded dynamically from S3:

  • Deploy and update independently
  • No application redeployment needed
  • Standard web components or Svelte compiled to web components
  • Slightly slower initial load (cached after first use)

Semantic markup that doesn't render visually:

  • Useful for analytics tracking
  • Metadata extraction
  • Non-visual agent communication

Components specify which display contexts they support:

renderingContexts: {
inline: { enabled: true }, // Within message flow
dialog: { enabled: true }, // Modal overlay
spotlight: { enabled: true }, // Focused view
canvas: { enabled: true } // Full-screen experience
}

Inline Context:

  • Component appears within the message flow
  • Maintains conversation context
  • Suitable for compact visualizations

Dialog Context:

  • Overlays the chat interface
  • Focus user attention
  • Ideal for forms and multi-step interactions

Spotlight Context:

  • Dedicated focus area alongside chat
  • Persistent across messages
  • Great for dashboards and monitoring

Canvas Context:

  • Full-screen immersive experience
  • Maximum screen real estate
  • Perfect for complex interactions and visualizations

Components can request structured data from the agent:

class DataWidget extends HTMLElement {
async loadData() {
// Component invokes agent for structured data
const response = await this.pikaContext.invokeAgent({
componentAgentInstructionsMd: `Generate product data matching: ${this.query}`,
outputSchema: ProductSchema,
conversationId: this.conversationId
});
this.render(response.data);
}
}

The agent receives:

  • Component's specific instructions
  • TypeScript interface defining expected output
  • Current conversation context

The agent returns structured JSON matching the interface.

Handle real-time content delivery gracefully:

connectedCallback() {
const content = this.textContent;
const status = this.getAttribute('streaming-status');
if (status === 'pending') {
this.showLoadingState();
} else if (status === 'completed') {
this.render(JSON.parse(content));
} else if (status === 'error') {
this.showError();
}
}

Streaming states:

  • pending: Content still streaming, show loading UI
  • completed: Full content received, render normally
  • error: Stream failed, show error state

Product Discovery

Component: Interactive product cards with images, pricing, ratings

Agent Usage: "Here are the top-rated laptops in your price range:" + product card components

Features: Add to cart, compare products, view details

Order Tracking

Component: Real-time order status with shipping timeline

Agent Usage: "Your order is:" + order status component

Features: Live updates, delivery map, contact carrier

Portfolio Dashboard

Component: Interactive portfolio visualization with drill-down

Agent Usage: "Your portfolio performance:" + portfolio component

Features: Asset allocation charts, historical performance, what-if analysis

Loan Calculator

Component: Interactive loan calculator with amortization schedule

Agent Usage: "Let me help you calculate that:" + calculator component

Features: Real-time calculations, comparison scenarios, application starter

Appointment Scheduler

Component: Calendar-based appointment booking

Agent Usage: "Available appointments:" + scheduler component

Features: Real-time availability, provider selection, confirmation

Medication Tracker

Component: Visual medication schedule and reminders

Agent Usage: "Your medication schedule:" + tracker component

Features: Dosage tracking, refill reminders, interaction warnings

Rich Interactions

Natural conversation combined with powerful interactive UI creates intuitive experiences that feel both conversational and productive.

Context-Aware UI

Agents render appropriate components based on user needs rather than forcing navigation through predetermined interfaces.

Reusable Components

Build components once, use across multiple agents and chat apps. Standard web component technology means portability and reusability.

Independent Deployment

Update web components without redeploying your application. Deploy new features to production instantly.

Standard Technologies

Use familiar web technologies: HTML, CSS, JavaScript, or compile from Svelte, React, Vue, or any framework.

In your pika-config.ts:

export const siteFeatures: SiteFeatures = {
tags: {
enabled: true,
tagsEnabled: [
{ scope: 'pika', tag: 'chart' },
{ scope: 'pika', tag: 'download' }
]
}
};

Create a standard web component:

class ProductCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const data = JSON.parse(this.textContent);
this.render(data);
}
render(product) {
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
max-width: 320px;
}
.product-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
}
.product-name {
font-size: 18px;
font-weight: 600;
margin: 12px 0 8px;
}
.product-price {
font-size: 24px;
color: #059669;
font-weight: 700;
}
</style>
<div class="card">
<img class="product-image"
src="${product.imageUrl}"
alt="${product.name}">
<div class="product-name">${product.name}</div>
<div class="product-price">$${product.price}</div>
<button class="add-to-cart">Add to Cart</button>
</div>
`;
this.shadowRoot.querySelector('.add-to-cart')
.addEventListener('click', () => this.addToCart(product));
}
addToCart(product) {
// Handle add to cart logic
console.log('Adding to cart:', product);
}
}
customElements.define('product-card', ProductCard);

Upload to S3 and create a tag definition:

{
tag: 'product-card',
scope: 'custom',
usageMode: 'chat-app',
widget: {
type: 'web-component',
webComponent: {
s3Bucket: 'my-components-bucket',
s3Key: 'product-card.js',
encoding: 'gzip'
}
},
renderingContexts: {
inline: { enabled: true },
dialog: { enabled: true }
},
llmInstructionsMd: `Use the product-card tag to display product information.
Example:
<product-card>{"name":"Laptop","price":999.99,"imageUrl":"https://..."}</product-card>`,
status: 'enabled'
}
{
chatAppId: 'shopping-assistant',
features: {
tags: {
tagsEnabled: [
{ scope: 'custom', tag: 'product-card' }
]
}
}
}

Create components that request structured data from the agent:

class DynamicDataTable extends HTMLElement {
async connectedCallback() {
const query = this.getAttribute('data-query');
const schema = this.getAttribute('data-schema');
// Request structured data from agent
const response = await this.pikaContext.invokeAgent({
componentAgentInstructionsMd: `Retrieve data for: ${query}`,
outputSchema: JSON.parse(schema),
conversationId: this.conversationId
});
this.renderTable(response.data);
}
}

Adapt rendering based on display context:

connectedCallback() {
const context = this.getAttribute('pika-context');
switch (context) {
case 'inline':
this.renderCompact();
break;
case 'dialog':
this.renderDetailed();
break;
case 'spotlight':
this.renderDashboard();
break;
case 'canvas':
this.renderFullscreen();
break;
}
}

Access conversation state and send messages:

class InteractiveWidget extends HTMLElement {
handleUserAction(action) {
// Send message to agent
this.pikaContext.sendMessage(
`User selected: ${action}`
);
// Update component state
this.updateState(action);
}
}
  1. Agent Decision: Agent determines component is needed
  2. Tag Output: Agent includes component tag in response
  3. Parser Detection: Message parser identifies component tag
  4. Definition Lookup: System retrieves tag definition
  5. Component Loading: Web component loaded from S3 (if not cached)
  6. Instantiation: Component element created and attached
  7. Rendering: Component renders based on content and context
  8. Streaming Updates: Component receives streaming status updates
  9. Finalization: Component transitions to completed state
interface TagDefinition {
tag: string; // Component name
scope: string; // Namespace (e.g., 'custom', 'pika')
usageMode: 'global' | 'chat-app'; // Availability model
widget: {
type: 'web-component';
webComponent: {
s3Bucket: string; // S3 bucket name
s3Key: string; // Component file path
encoding: 'gzip' | 'none'; // Compression
};
};
renderingContexts: {
inline: { enabled: boolean };
dialog: { enabled: boolean };
spotlight: { enabled: boolean };
canvas: { enabled: boolean };
};
llmInstructionsMd: string; // Instructions for agent
status: 'enabled' | 'disabled'; // Lifecycle status
}

For high-performance components, compile directly into your application:

// In customRenderers registry
import ProductCard from './ProductCard.svelte';
export const customRenderers = {
'product-card': ProductCard
};
// Tag definition
{
tag: 'product-card',
scope: 'custom',
widget: {
type: 'custom-compiled-in'
}
}

Existing compiled-in components can evolve to web components:

  1. Start: Custom Svelte component compiled into application
  2. Add Tag Definition: Create tag definition with type: 'custom-compiled-in'
  3. Build Web Component: Compile Svelte to web component
  4. Upload to S3: Deploy as standalone component
  5. Update Tag Definition: Change to type: 'web-component'
  6. Deploy: Component now updates independently

No changes to component code required during transition.

Ready to build custom web components?

Implementation Guide

Step-by-step instructions for building and deploying components.

View How-To →

Reference Documentation

Complete API reference for component development.

View Reference →