Complete reference for Pika's tag definition system that enables AI-driven custom UI components.
Overview
Section titled “Overview”The widget system allows LLMs to generate custom UI elements by including special tags in their responses. Each tag is defined by a TagDefinition that specifies:
- Widget type and rendering
- When and where it can be used
- Instructions for the LLM
- Access control
Import Statements
Section titled “Import Statements”Tag Definition Types
Section titled “Tag Definition Types”import type { TagDefinition, TagDefinitionLite, TagDefinitionWidget, TagInstructionForLlm, WidgetRenderingContexts} from 'pika-shared/types/chatbot/chatbot-types';Widget and Metadata Types
Section titled “Widget and Metadata Types”import type { WidgetAction, WidgetMetadata, WidgetMetadataState, SpotlightWidgetDefinition, WidgetCallbackContext} from 'pika-shared/types/chatbot/webcomp-types';Tag Definition Types
Section titled “Tag Definition Types”TagDefinition
Section titled “TagDefinition”Complete tag definition with all configuration.
interface TagDefinition<T extends TagDefinitionWidget> { tag: string; scope: string; usageMode: 'global' | 'chat-app'; widget: T; llmInstructionsMd?: string; status: 'enabled' | 'disabled' | 'retired'; renderingContexts: WidgetRenderingContexts; canBeGeneratedByLlm: boolean; canBeGeneratedByTool: boolean; description: string; dontCacheThis?: boolean; createdBy: string; lastUpdatedBy: string; createDate: string; lastUpdate: string;}Fields:
tag- Tag name (e.g.,'chart','order-status')scope- Namespace to prevent collisions (e.g.,'pika','custom')usageMode-'global'(auto-available) or'chat-app'(explicit enable)widget- Widget configuration (type and settings)llmInstructionsMd- Markdown instructions injected into promptsstatus- Lifecycle staterenderingContexts- Where widget can be renderedcanBeGeneratedByLlm- Whether LLM can use this tagcanBeGeneratedByTool- Whether tools can generate this tagdescription- Admin-facing description
TagDefinitionLite
Section titled “TagDefinitionLite”Minimal tag identifier.
interface TagDefinitionLite { scope: string; tag: string;}Used for referencing tags without full definition.
Widget Types
Section titled “Widget Types”Four types of widgets are supported:
type TagDefinitionWidget = | BuiltInWidget | CustomCompiledInWidget | WebComponentWidget | PassThroughWidget;BuiltInWidget
Section titled “BuiltInWidget”Pika-provided widgets (chart, image, prompt).
interface BuiltInWidget { type: 'built-in'; builtInType: 'chart' | 'image' | 'prompt';}CustomCompiledInWidget
Section titled “CustomCompiledInWidget”Svelte components compiled into your app.
interface CustomCompiledInWidget { type: 'custom-compiled-in';}References renderers in customRenderers registry.
WebComponentWidget
Section titled “WebComponentWidget”Dynamically loaded web components.
interface WebComponentWidget { type: 'web-component'; webComponent: { s3Bucket: string; s3Key: string; encoding?: 'gzip' | 'none'; };}PassThroughWidget
Section titled “PassThroughWidget”Non-rendering semantic markup.
interface PassThroughWidget { type: 'pass-through';}Usage Modes
Section titled “Usage Modes”Global Tags
Section titled “Global Tags”Automatically available to all chat apps unless explicitly disabled.
const globalTag: TagDefinition<BuiltInWidget> = { tag: 'chart', scope: 'pika', usageMode: 'global', // Available everywhere by default status: 'enabled', widget: { type: 'built-in', builtInType: 'chart' }, // ... other fields};Disabling global tags per chat app:
// In chat app configurationfeatures: { tags: { enabled: true, tagsDisabled: [ { scope: 'pika', tag: 'chart' } // Disable charts in this app ] }}Chat-App Tags
Section titled “Chat-App Tags”Must be explicitly enabled per chat app.
const chatAppTag: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'acme', usageMode: 'chat-app', // Requires explicit enable status: 'enabled', widget: { type: 'web-component', webComponent: { s3Bucket: 'acme-widgets', s3Key: 'order-status.js', encoding: 'gzip' } }, // ... other fields};Enabling chat-app tags:
// In chat app configurationfeatures: { tags: { enabled: true, tagsEnabled: [ { scope: 'acme', tag: 'order-status' } ] }}Rendering Contexts
Section titled “Rendering Contexts”Control where widgets can be rendered.
WidgetRenderingContexts
Section titled “WidgetRenderingContexts”interface WidgetRenderingContexts { inline?: InlineContextConfig; canvas?: CanvasContextConfig; dialog?: DialogContextConfig; spotlight?: SpotlightContextConfig; static?: StaticContextConfig; hero?: HeroContextConfig;}Contexts:
inline- Within message text flowcanvas- Right-side panel, resizable with chat panedialog- Modal/popup overlayspotlight- Featured carousel above chat inputstatic- Hidden widget that runs initialization code (no visual UI)hero- Singleton widget displayed prominently below spotlight (v0.17.0+)
Example:
renderingContexts: { inline: { enabled: true }, canvas: { enabled: true }, dialog: { enabled: false }, spotlight: { enabled: false }, static: { enabled: false }, hero: { enabled: false }}Hero Context (v0.17.0+)
Section titled “Hero Context (v0.17.0+)”Hero is a new rendering context for a dominant, singleton widget that displays above the chat input area:
interface HeroContextConfig { enabled: boolean; /** If true, hero is auto-rendered on startup. @default false */ autoCreateInstance?: boolean; /** If true, hero starts collapsed on startup. @default false */ startCollapsed?: boolean; /** Sizing configuration for width and height constraints */ sizing?: HeroSizeConfig;}
/** * Hero sizing configuration (v0.18.0+) * Controls the dimensions of the hero widget container. * Hero is always horizontally centered; sizing controls the max bounds. */interface HeroSizeConfig { /** Fixed width (e.g., '600px', '80%'). If not set, uses content width. */ width?: string; /** Fixed height (e.g., '300px', 'auto'). If not set, uses content height. */ height?: string; /** Minimum width constraint. @default '200px' */ minWidth?: string; /** Maximum width constraint. @default '90%' */ maxWidth?: string; /** Minimum height in pixels. @default 100 */ minHeight?: number; /** Maximum height in pixels. @default 600 */ maxHeight?: number;}Key characteristics:
- Singleton - Only one hero widget can exist at a time
- API controlled - Shown/hidden via
showHero(),hideHero(),closeHero() - Collapsible - Users can collapse to a compact header bar
- Can be destroyed - Unlike spotlight, hero can be completely removed
Example:
// Render a hero widgetawait chatAppState.renderTag('acme.dashboard', 'hero', { userId: '123' }, { title: 'Dashboard', lucideIconName: 'layout-dashboard'});
// Collapse to header barchatAppState.collapseHero();
// Expand from collapsed statechatAppState.expandHero();
// Toggle collapsed/expandedchatAppState.toggleHeroCollapsed();
// Hide the hero completely (programmatic only - no UI)chatAppState.hideHero();
// Show it againchatAppState.showHero();
// Destroy completelychatAppState.closeHero();Hero state properties:
// Check if hero is visible (false = completely hidden, no UI)const isVisible = chatAppState.heroVisible;
// Check if hero is collapsed to header barconst isCollapsed = chatAppState.heroCollapsed;Hero display states:
- Hidden (heroVisible=false): No UI shown - widget runs in background (programmatic only)
- Collapsed (heroVisible=true, heroCollapsed=true): Shows header bar with title and expand caret
- Expanded (heroVisible=true, heroCollapsed=false): Shows full widget with header and content
User can only toggle between collapsed and expanded states. Widgets can programmatically show/hide/collapse/expand.
Hero Sizing (v0.18.0+):
The hero widget container is always horizontally centered. Use sizing config to control dimensions:
// Tag definition with sizingrenderingContexts: { hero: { enabled: true, sizing: { // Content-driven width with constraints minWidth: '400px', maxWidth: '900px', // Height constraints minHeight: 150, maxHeight: 400 } }}Sizing behavior:
- If
width/heightare not set, the hero uses the content's intrinsic size (clamped to min/max) - If
width/heightare set, those exact values are used (still clamped to min/max) - Percentage values (e.g.,
'80%','100%') are responsive to viewport changes maxWidth: '100%'ensures the hero never overflows on small screens
Example sizing configurations:
// Compact hero (fixed width)sizing: { width: '500px', minHeight: 100, maxHeight: 300 }
// Full-width hero with height limitssizing: { maxWidth: '100%', minHeight: 200, maxHeight: 500 }
// Responsive hero with constraintssizing: { minWidth: '400px', maxWidth: '900px', minHeight: 150, maxHeight: 400 }Spotlight Context
Section titled “Spotlight Context”Spotlight is a carousel of widgets displayed above the chat input area:
interface SpotlightContextConfig { enabled: boolean; /** If true, widget appears first in carousel. @default false */ isDefault?: boolean; /** Display order in carousel (lower = higher). @default 0 */ displayOrder?: number; /** If true (default), only one instance can exist. @default true */ singleton?: boolean; /** If false, widget won't appear in unpinned menu. @default true */ showInUnpinnedMenu?: boolean; /** If true (default), widget is auto-created on startup. @default true */ autoCreateInstance?: boolean; /** If true, spotlight starts collapsed on startup. @default false */ startCollapsed?: boolean;}Spotlight API:
// Show the spotlight carouselchatAppState.showSpotlight();
// Hide the spotlight to just headerchatAppState.hideSpotlight();
// Toggle spotlight visibilitychatAppState.toggleSpotlight();
// Check if spotlight is visibleconst isVisible = chatAppState.spotlightVisible;Static Context
Section titled “Static Context”Static widgets run initialization code but don't render visually. Useful for orchestration:
interface StaticContextConfig { enabled: boolean; shutDownAfterMs?: number; // Remove from DOM after this many ms}Use cases:
- Register title bar actions
- Set up event listeners
- Initialize shared state
- Coordinate between multiple widgets
LLM Instructions
Section titled “LLM Instructions”Guide the LLM on when and how to use tags.
TagInstructionForLlm
Section titled “TagInstructionForLlm”type TagInstructionForLlm = | { type: 'line'; text: string } | { type: 'block'; title: string; lines: Array<{ type: 'line'; text: string }>; };Example Instructions
Section titled “Example Instructions”llmInstructions: [ { type: 'line', text: 'Use the chart tag when you need to visualize numerical data' }, { type: 'block', title: 'Chart Types', lines: [ { type: 'line', text: '- bar: For comparing categories' }, { type: 'line', text: '- line: For showing trends over time' }, { type: 'line', text: '- pie: For showing proportions' } ] }, { type: 'line', text: 'Example: <chart type="bar" data=\'{"labels":["A","B"],"datasets":[{"data":[1,2]}]}\' />' }]Markdown Format
Section titled “Markdown Format”Instructions can also be provided as Markdown:
llmInstructionsMd: `# Chart Tag
Use the chart tag when you need to visualize numerical data.
## Chart Types- **bar**: For comparing categories- **line**: For showing trends over time- **pie**: For showing proportions
## Example\`\`\`xml<chart type="bar" data='{"labels":["A","B"],"datasets":[{"data":[1,2]}]}' />\`\`\``Lifecycle Management
Section titled “Lifecycle Management”Status Values
Section titled “Status Values”type TagStatus = 'enabled' | 'disabled' | 'retired';enabled- Active and availabledisabled- Temporarily hiddenretired- Permanently archived
Complete Examples
Section titled “Complete Examples”Built-In Chart Tag
Section titled “Built-In Chart Tag”const chartTag: TagDefinition<BuiltInWidget> = { tag: 'chart', scope: 'pika', usageMode: 'global', widget: { type: 'built-in', builtInType: 'chart' }, llmInstructionsMd: `Use for data visualization. Supports bar, line, and pie charts.`, status: 'enabled', renderingContexts: { inline: false, canvas: true, dialog: true, spotlight: true }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Renders interactive charts', createdBy: 'system', lastUpdatedBy: 'system', createDate: '2024-01-01T00:00:00Z', lastUpdate: '2024-01-01T00:00:00Z'};Custom Web Component Tag
Section titled “Custom Web Component Tag”const orderStatusTag: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'acme', usageMode: 'chat-app', widget: { type: 'web-component', webComponent: { s3Bucket: 'acme-components', s3Key: 'widgets/order-status-v1.js', encoding: 'gzip' } }, llmInstructionsMd: `# Order Status Widget
Display real-time order status with tracking information.
## Usage\`\`\`xml<order-status orderId="12345" />\`\`\`
## Attributes- **orderId** (required): The order ID to display`, status: 'enabled', renderingContexts: { inline: true, canvas: true, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Shows order status and tracking', dontCacheThis: false, createdBy: 'admin@acme.com', lastUpdatedBy: 'admin@acme.com', createDate: '2024-01-15T10:00:00Z', lastUpdate: '2024-01-15T10:00:00Z'};Custom Compiled-In Tag
Section titled “Custom Compiled-In Tag”const dataTableTag: TagDefinition<CustomCompiledInWidget> = { tag: 'data-table', scope: 'custom', usageMode: 'chat-app', widget: { type: 'custom-compiled-in' // References customRenderers['data-table'] }, llmInstructionsMd: `Display tabular data with headers and rows.
Example:\`\`\`xml<data-table>{ "headers": ["Name", "Age", "City"], "rows": [ ["John", "30", "NYC"], ["Jane", "25", "LA"] ]}</data-table>\`\`\``, status: 'enabled', renderingContexts: { inline: false, canvas: true, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Renders data tables', createdBy: 'admin', lastUpdatedBy: 'admin', createDate: '2024-01-15T10:00:00Z', lastUpdate: '2024-01-15T10:00:00Z'};Pass-Through Metadata Tag
Section titled “Pass-Through Metadata Tag”const metadataTag: TagDefinition<PassThroughWidget> = { tag: 'metadata', scope: 'system', usageMode: 'global', widget: { type: 'pass-through' }, llmInstructionsMd: 'Use for semantic markup that should not render visually', status: 'enabled', renderingContexts: { inline: true, canvas: false, dialog: false, spotlight: false }, canBeGeneratedByLlm: true, canBeGeneratedByTool: true, description: 'Semantic markup with no visual rendering', createdBy: 'system', lastUpdatedBy: 'system', createDate: '2024-01-01T00:00:00Z', lastUpdate: '2024-01-01T00:00:00Z'};Tag Management APIs
Section titled “Tag Management APIs”Admin APIs
Section titled “Admin APIs”Full access to tag definitions (requires admin permissions).
Create or Update:
POST /api/chat-admin/tagdef{ "tagDefinition": { /* TagDefinition object */ }, "userId": "admin-user"}Delete:
DELETE /api/chat-admin/tagdef{ "tagDefinition": { "tag": "my-tag", "scope": "custom" }, "userId": "admin-user"}Search (including disabled):
POST /api/chat-admin/tagdef/search{ "includeInstructions": true, "paginationToken": null}Chat APIs
Section titled “Chat APIs”Read-only access to enabled tags.
Search (enabled only):
POST /api/chat/tagdef/search{ "includeInstructions": false, "paginationToken": null}Web Component Development
Section titled “Web Component Development”Creating a Web Component
Section titled “Creating a Web Component”class OrderStatusWidget extends HTMLElement { connectedCallback() { const orderId = this.getAttribute('orderId'); this.render(orderId); }
async render(orderId) { const status = await this.fetchOrderStatus(orderId);
this.innerHTML = ` <div class="order-status"> <h3>Order #${orderId}</h3> <p>Status: ${status.state}</p> <p>Tracking: ${status.tracking}</p> </div> `; }
async fetchOrderStatus(orderId) { // Fetch from API return { state: 'Shipped', tracking: '1Z999...' }; }}
customElements.define('order-status-widget', OrderStatusWidget);Uploading to S3
Section titled “Uploading to S3”# Compress and uploadgzip order-status-widget.jsaws s3 cp order-status-widget.js.gz s3://my-bucket/widgets/ \ --content-encoding gzip \ --content-type application/javascriptRegistering the Tag
Section titled “Registering the Tag”const tagDef: TagDefinition<WebComponentWidget> = { tag: 'order-status', scope: 'custom', usageMode: 'chat-app', widget: { type: 'web-component', webComponent: { s3Bucket: 'my-bucket', s3Key: 'widgets/order-status-widget.js.gz', encoding: 'gzip' } }, // ... other fields};
// POST to /api/chat-admin/tagdefDynamic Spotlight Registration
Section titled “Dynamic Spotlight Registration”Components can register themselves in spotlight programmatically at runtime without creating database tag definitions.
manuallyRegisterSpotlightWidget()
Section titled “manuallyRegisterSpotlightWidget()”Register a web component as a spotlight widget from code.
manuallyRegisterSpotlightWidget(definition: SpotlightWidgetDefinition): voidParameters:
interface SpotlightWidgetDefinition { /** Tag name (e.g., 'my-widget') */ tag: string;
/** Scope/namespace (e.g., 'my-app') */ scope: string;
/** Display title in UI */ tagTitle: string;
/** Custom element name (optional, inferred if not provided) */ customElementName?: string;
/** Widget sizing configuration (optional) */ sizing?: WidgetSizing;
/** Component agent instructions (optional) */ componentAgentInstructionsMd?: Record<string, string>;
/** * Auto-show widget immediately? * - true: Widget appears in spotlight automatically * - false: Widget registered but not shown until renderTag() called * @default true */ autoCreateInstance?: boolean;
/** * Display order in spotlight (lower = higher) * @default 0 */ displayOrder?: number;
/** * Only allow one instance? * @default true */ singleton?: boolean;
/** * Show in unpinned widget menu? * @default true */ showInUnpinnedMenu?: boolean;
/** * Optional initial metadata (title, actions, icon) to apply when rendered. * Metadata can be updated later via getWidgetMetadataAPI(). * @since 0.11.0 */ metadata?: WidgetMetadata;}Behavior:
- Registration is ephemeral - does not persist across page refreshes
- Component must re-register on each page load
- Merges with database-sourced tag definitions
- Respects user preferences (unpinning, ordering)
- Compatible with all spotlight features
Example:
import { getPikaContext } from 'pika-shared/util/wc-utils';
class MyWidget extends HTMLElement { async connectedCallback() { const context = await getPikaContext(this);
// Register in spotlight context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'my-widget', scope: 'acme', tagTitle: 'My Widget', customElementName: 'acme-my-widget', displayOrder: 0, autoCreateInstance: true }); }}Common Use Cases:
// Conditional registration (admin-only)if (context.appState.identity.isSiteAdmin) { context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'admin-tools', scope: 'internal', tagTitle: 'Admin Tools' });}
// Feature-gated widgetconst features = await getEnabledFeatures();if (features.includes('beta-analytics')) { context.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'analytics', scope: 'acme', tagTitle: 'Analytics Dashboard', displayOrder: 5 });}
// Base widget for saved instancescontext.chatAppState.manuallyRegisterSpotlightWidget({ tag: 'saved-chart', scope: 'acme', tagTitle: 'Saved Chart', autoCreateInstance: false, // Don't show by default singleton: false, // Allow multiple instances showInUnpinnedMenu: false // Hide from menu});Best Practices:
Widget Metadata and Actions
Section titled “Widget Metadata and Actions”WidgetMetadata Interface
Section titled “WidgetMetadata Interface”Widgets can dynamically update their chrome (title bar, actions, loading state):
interface WidgetMetadata { /** Widget title shown in chrome */ title: string;
/** * Optional Lucide icon name (fetched automatically and set as iconSvg). * Use snake-case: 'arrow-big-down' not 'arrowBigDown' */ lucideIconName?: string;
/** Optional icon SVG markup for the widget title */ iconSvg?: string;
/** Optional color for the widget icon (hex, rgb, or CSS color name) */ iconColor?: string;
/** Optional action buttons */ actions?: WidgetAction[];
/** Optional loading status */ loadingStatus?: { loading: boolean; loadingMsg?: string; };}WidgetAction Interface
Section titled “WidgetAction Interface”Action buttons that appear in widget chrome (spotlight, canvas, dialog):
interface WidgetAction { /** Unique identifier for this action */ id: string;
/** Tooltip/label for the action (also button text in dialog context) */ title: string;
/** SVG markup string for the icon */ iconSvg: string;
/** Whether action is currently disabled */ disabled?: boolean;
/** If true, renders as default/prominent button (dialog footer) */ primary?: boolean;
/** * Handler called when action is clicked. * Automatically receives widget context with element, instanceId, and full Pika context. * @since 0.11.0 - Callback now receives WidgetCallbackContext parameter */ callback: (context: WidgetCallbackContext) => void | Promise<void>;}WidgetCallbackContext Interface
Section titled “WidgetCallbackContext Interface”Context automatically provided to action button callbacks:
interface WidgetCallbackContext { /** The web component element */ element: HTMLElement;
/** The unique instance ID assigned to this component */ instanceId: string;
/** The full Pika context with app state, chat state, and more */ context: PikaWCContext;}Action Button Example
Section titled “Action Button Example”import { extractIconSvg } from 'pika-shared/util/icon-utils';
// Define an actionconst refreshAction: WidgetAction = { id: 'refresh', title: 'Refresh Data', iconSvg: await extractIconSvg('refresh-cw', 'lucide'), disabled: false, callback: async ({ element, instanceId, context }) => { // Access the widget element const widget = element as MyWidget;
// Show loading state const metadata = context.chatAppState.getWidgetMetadataAPI( 'my-app', 'my-widget', instanceId, context.renderingContext ); metadata.setLoadingStatus(true, 'Refreshing...');
try { // Refresh data await widget.refreshData(); context.appState.showToast('Data refreshed!', { type: 'success' }); } catch (error) { context.appState.showToast('Refresh failed', { type: 'error' }); } finally { metadata.setLoadingStatus(false); } }};
// Register metadata with actionconst metadata: WidgetMetadata = { title: 'My Widget', lucideIconName: 'chart-line', actions: [refreshAction]};
// Apply metadata when renderingawait context.chatAppState.renderTag('my-app.my-widget', 'canvas', { data }, metadata);Rendering Widgets with Metadata
Section titled “Rendering Widgets with Metadata”The renderTag() method now accepts optional metadata:
// Render with initial metadataawait chatAppState.renderTag( 'acme.dashboard', 'canvas', { userId: '123' }, { title: 'Sales Dashboard', lucideIconName: 'bar-chart', actions: [ { id: 'export', title: 'Export Data', iconSvg: await extractIconSvg('download', 'lucide'), callback: async ({ context }) => { // Export logic here } } ] });Metadata can be:
- Passed initially via
renderTag()(as shown above) - Updated dynamically via
getWidgetMetadataAPI().setMetadata() - Applied when manually registering spotlight widgets via
SpotlightWidgetDefinition.metadata
Auto-Generated Tag Definitions
Section titled “Auto-Generated Tag Definitions”When renderTag() is called with 'canvas' or 'dialog' context for a tag that doesn't exist, the system automatically generates a minimal tag definition:
// This works even if 'acme.custom-widget' isn't registered in the databaseawait chatAppState.renderTag('acme.custom-widget', 'canvas', { data });Auto-generated tags include:
- Minimal required fields (scope, tag, status)
- The requested rendering context enabled
usageMode: 'chat-app'status: 'enabled'
Canvas Widget Options (v0.17.0+)
Section titled “Canvas Widget Options (v0.17.0+)”Canvas widgets can be rendered with additional options to enable advanced UI patterns.
CanvasWidgetOptions Interface
Section titled “CanvasWidgetOptions Interface”interface CanvasWidgetOptions { /** Enter companion mode - compact chat pane while canvas is primary */ companionMode?: boolean;
/** Start with chat pane minimized (requires companionMode) */ chatPaneMinimized?: boolean;
/** Widget provides its own chrome (hides framework title bar) */ fullControl?: boolean;
/** Configure close behavior */ closeConfig?: CanvasCloseConfig;}Companion Mode
Section titled “Companion Mode”Companion mode creates an "application-first" experience where the canvas widget is the primary focus and the chat becomes a compact assistant pane:
// Render canvas in companion modeawait chatAppState.renderTag('acme.dashboard', 'canvas', { companionMode: true, chatPaneMinimized: false // Start with chat visible});When companion mode is active:
- Chat pane becomes compact with reduced padding
- Spotlight widgets are hidden
- Hero widget is hidden
- Chat history is minimized
- User can minimize chat to a ~20px strip
Full Control Mode
Section titled “Full Control Mode”Full control mode lets the widget render its own chrome (title bar, close button):
await chatAppState.renderTag('acme.ide', 'canvas', { fullControl: true, closeConfig: { confirmOnClose: true, confirmTitle: 'Unsaved Changes', confirmMessage: 'You have unsaved work. Close anyway?' }});When fullControl is enabled:
- Framework title bar is hidden
- Widget is responsible for its own UI chrome
- Use
requestCanvasClose()to trigger close with confirmation
Close Configuration
Section titled “Close Configuration”Configure how the canvas behaves when closing:
interface CanvasCloseConfig { /** Show confirmation dialog before closing */ confirmOnClose?: boolean;
/** Title for confirmation dialog */ confirmTitle?: string;
/** Message for confirmation dialog */ confirmMessage?: string;
/** Label for confirm button */ confirmButtonLabel?: string;
/** Label for cancel button */ cancelButtonLabel?: string;}Using requestCanvasClose():
For fullControl widgets that need to trigger the framework's close confirmation:
// In a fullControl widget's close button handlerasync function handleClose() { const shouldClose = await context.chatAppState.requestCanvasClose(); // If shouldClose is true, framework will handle the close // If false, user cancelled}Event System (v0.17.0+)
Section titled “Event System (v0.17.0+)”Widgets can subscribe to framework events to react to state changes.
Available Events
Section titled “Available Events”interface ChatAppEvents { // Widget lifecycle events widgetOpen: { tagId: string; instanceId: string; renderingContext: WidgetRenderingContextType }; widgetClose: { tagId: string; instanceId: string; renderingContext: WidgetRenderingContextType }; widgetReady: { tagId: string; instanceId: string; renderingContext: WidgetRenderingContextType }; // v0.18.0+
// Canvas events canvasOpen: { tagId: string; instanceId?: string }; canvasClose: { tagId: string; instanceId?: string };
// Chat pane events chatPaneMinimized: Record<string, never>; chatPaneExpanded: Record<string, never>;
// Companion mode events companionModeEnter: Record<string, never>; companionModeExit: Record<string, never>;
// Hero widget events (v0.18.0+) heroWillShow: Record<string, never>; // Before hero starts showing heroDidShow: Record<string, never>; // After hero is shown heroWillHide: Record<string, never>; // Before hero starts hiding heroDidHide: Record<string, never>; // After hero is hidden heroCollapse: Record<string, never>; // When hero is collapsed to header bar heroExpand: Record<string, never>; // When hero is expanded from collapsed
// Spotlight events (v0.18.0+) spotlightShow: Record<string, never>; spotlightHide: Record<string, never>;}Widget Ready Event (v0.18.0+)
Section titled “Widget Ready Event (v0.18.0+)”Widgets can signal when they've finished loading and are ready to receive commands. This is a best practice for widgets that load data asynchronously.
const context = await getPikaContext(this);const { chatAppState, instanceId, tagId } = context;
console.log(`Widget ${tagId} initializing...`);
// After your widget has finished initializingawait this.loadData();this.renderUI();
// Signal that widget is readychatAppState.signalWidgetReady(instanceId);Why use widgetReady?
- Agents or orchestrators can wait for a widget to be ready before issuing commands
- Prevents race conditions where commands are sent before data is loaded
- Enables sequential widget interactions (e.g., "show tab 3" after widget loads)
Listening for widget ready:
chatAppState.addEventListener('widgetReady', ({ tagId, instanceId }) => { if (tagId === 'acme.dashboard') { // Dashboard is ready, safe to send commands chatAppState.sendCommandToWidget(instanceId, { action: 'showTab', tab: 3 }); }}, myInstanceId);Widget Tag ID (v0.18.0+)
Section titled “Widget Tag ID (v0.18.0+)”The tagId property is automatically provided in the Pika context when a widget is injected. This eliminates the need to hardcode your tag ID:
const ctx = await getPikaContext(this);
// ctx.tagId contains your widget's scope.tag, e.g., 'myapp.orchestrator'console.log(`I am: ${ctx.tagId}`);
// Useful for Intent Router handler registration - no hardcoding neededctx.chatAppState.registerIntentRouterHandler( ctx.instanceId, ctx.tagId, // Automatically matches your tag definition handleCommand);Suggest Question API (v0.18.0+)
Section titled “Suggest Question API (v0.18.0+)”Widgets can pre-fill the chat input to help users ask contextual questions. This is useful for AI assist buttons that suggest follow-up queries:
// Suggest a question - expands chat pane, fills input, and highlights itchatAppState.suggestQuestion( 'What does this weather pattern mean for outdoor activities?');
// With optionschatAppState.suggestQuestion('Help me understand these errors', { focus: true, // Focus the input (default: true) highlight: true, // Briefly highlight the input (default: true) expandChatPane: true // Expand if minimized in companion mode (default: true)});Example: AI Assist Button
const aiAssistAction: WidgetAction = { id: 'ai-assist', title: 'Ask AI about this', iconSvg: await getIconSvg('sparkles', 'lucide'), callback: async () => { // Build contextual question based on widget state const question = `Based on the data showing ${this.summary}, what should I do next?`; context.chatAppState.suggestQuestion(question); }};Hero Lifecycle Events (v0.18.0+)
Section titled “Hero Lifecycle Events (v0.18.0+)”The hero widget emits detailed lifecycle events for coordinating animations and state. Since hero widgets persist when hidden (v0.18.2+), these events are essential for managing data refresh and resource usage:
// Prepare before hero shows (e.g., pause other animations)chatAppState.addEventListener('heroWillShow', () => { this.pauseAnimations();}, instanceId);
// React after hero is visible - IMPORTANT: refresh data herechatAppState.addEventListener('heroDidShow', () => { // Hero persists when hidden, so refresh data when shown again this.refreshData(); this.startDataSync();}, instanceId);
// Clean up before hero hideschatAppState.addEventListener('heroWillHide', () => { this.saveDraft();}, instanceId);
// React after hero is hidden - pause expensive operationschatAppState.addEventListener('heroDidHide', () => { // Hero is still mounted but hidden - stop unnecessary work this.stopDataSync(); this.pausePolling();}, instanceId);
// React to collapse/expandchatAppState.addEventListener('heroCollapse', () => { this.pauseExpensiveOperations();}, instanceId);
chatAppState.addEventListener('heroExpand', () => { this.resumeOperations();}, instanceId);Subscribing to Events
Section titled “Subscribing to Events”const context = await getPikaContext(this);const { chatAppState, instanceId } = context;
// Subscribe with instance ID for automatic cleanupconst unsubscribe = chatAppState.addEventListener('canvasOpen', (data) => { console.log(`Canvas opened: ${data.tagId}`);}, instanceId);
// Or manually unsubscribe laterunsubscribe();Event Usage Examples
Section titled “Event Usage Examples”// React to companion mode changeschatAppState.addEventListener('companionModeEnter', () => { // Adjust widget layout for full-width this.classList.add('expanded');}, instanceId);
chatAppState.addEventListener('companionModeExit', () => { this.classList.remove('expanded');}, instanceId);
// Track canvas lifecyclechatAppState.addEventListener('canvasClose', ({ tagId }) => { if (tagId === 'acme.editor') { // Save draft when editor closes this.saveDraft(); }}, instanceId);Best Practices
Section titled “Best Practices”Security Considerations
Section titled “Security Considerations”Troubleshooting
Section titled “Troubleshooting”Tag Not Rendering
Section titled “Tag Not Rendering”- Check tag is enabled:
status === 'enabled' - Verify usage mode and chat app configuration
- Check rendering context matches usage
- Ensure web component loaded successfully
LLM Not Using Tag
Section titled “LLM Not Using Tag”- Review
llmInstructionsMdclarity - Check
canBeGeneratedByLlm === true - Verify tag is in chat app's enabled list
- Test with explicit examples in prompt
Web Component Not Loading
Section titled “Web Component Not Loading”- Verify S3 bucket permissions
- Check S3 key path is correct
- Ensure CORS configured on bucket
- Verify encoding matches actual file
Related Documentation
Section titled “Related Documentation”- Widget Tag Guide - How-to guide for creating tags
- Web Components Guide - Building web components
- Custom Component Interface - Component API reference
- pika-ux Module - Pre-built UI components
- Chat App Configuration - Configuring tag features
Type Source Files
Section titled “Type Source Files”- chatbot-types.ts - Tag definition types
- webcomp-types.ts - Web component interfaces