@rameshnaik/prompt-ui
v0.1.5
Published
Framework-agnostic Web Component library for prompt-driven generative UI — charts, tables, list-detail views from natural language.
Downloads
61
Maintainers
Readme
@rameshnaik/prompt-ui
Framework-agnostic Web Component library for prompt-driven generative UI. Type a natural language prompt and get charts, tables, list-detail views, theme changes, and more — all generated on-the-fly from your data.
Works with React, Angular, Vue, Svelte, and vanilla HTML/JS — zero framework lock-in.
Preview
Installation
The library has three peer dependencies that power core features:
| Peer Dependency | Used For |
|---|---|
| chart.js | Rendering charts (pie, bar, line, doughnut, etc.) |
| jspdf | PDF export of generated widgets |
| html-to-image | Capturing widget DOM as image for PDF export |
npm install @rameshnaik/prompt-ui chart.js jspdf html-to-imageThis single command works for all frameworks — React, Angular, Vue, Svelte, and vanilla JS.
React users: Your project already has
reactinstalled. The library lists it as an optional peer dependency for version compatibility — you don't need to install it again.
Quick Start by Framework
All examples below use the same sample data and fields:
const fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
{ name: "Carol", department: "Sales", hours: 38 },
];React
The library ships a /react sub-entrypoint with a usePromptUI hook and built-in JSX type declarations — no boilerplate files needed.
import { usePromptUI } from "@rameshnaik/prompt-ui/react";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
const FIELDS: FieldDescriptor[] = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const DATA = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const EXAMPLES = ["Pie chart by department", "Sort by hours descending"];
export default function TimecardPage() {
const promptUI = usePromptUI({ fields: FIELDS });
return (
<>
<prompt-ui-bar ref={promptUI.bar} examplePrompts={EXAMPLES} />
<prompt-ui-canvas
ref={promptUI.canvas}
data={DATA as unknown as Record<string, unknown>[]}
fields={FIELDS}
/>
{/* ... rest of your page */}
</>
);
}What usePromptUI does:
- Creates a
PromptUIControllerinstance (once, persisted across re-renders) - Returns
promptUI.barandpromptUI.canvasref callbacks that wire the controller to each Web Component - Keeps
fieldsin sync when they change - Registers the custom elements automatically via side-effect import
Why
ref? React doesn't natively set JS object properties on custom elements. The ref callback bridges this gap. Other frameworks don't need it.
Angular
Angular binds properties to custom elements natively with [property] syntax. Just add CUSTOM_ELEMENTS_SCHEMA to your module or standalone component.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { PromptUIController } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";
@Component({
selector: "app-timecard",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<prompt-ui-bar
[controller]="controller"
[examplePrompts]="examples"
></prompt-ui-bar>
<prompt-ui-canvas
[controller]="controller"
[data]="data"
[fields]="fields"
></prompt-ui-canvas>
`,
})
export class TimecardComponent {
fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
examples = ["Pie chart by department", "Sort by hours descending"];
controller = new PromptUIController({ fields: this.fields });
}No additional adapters or wrappers required.
Vue
Vue supports Web Components natively with :property binding. Add a custom element config to your vite.config.ts to suppress unknown-tag warnings.
vite.config.ts
import vue from "@vitejs/plugin-vue";
export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith("prompt-ui") || tag === "viz-widget",
},
},
}),
],
};TimecardPage.vue
<script setup lang="ts">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";
const fields: FieldDescriptor[] = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const examples = ["Pie chart by department", "Sort by hours descending"];
const controller = new PromptUIController({ fields });
</script>
<template>
<prompt-ui-bar
:controller="controller"
:examplePrompts="examples"
/>
<prompt-ui-canvas
:controller="controller"
:data="data"
:fields="fields"
/>
</template>No additional adapters or wrappers required.
Svelte
Svelte treats custom elements as first-class citizens. Properties are passed directly.
<script lang="ts">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";
const fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const examples = ["Pie chart by department", "Sort by hours descending"];
const controller = new PromptUIController({ fields });
</script>
<prompt-ui-bar
controller={controller}
examplePrompts={examples}
/>
<prompt-ui-canvas
controller={controller}
{data}
{fields}
/>No configuration or adapters required.
Vanilla HTML / No Framework
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Prompt UI Demo</title>
</head>
<body>
<prompt-ui-bar id="bar"></prompt-ui-bar>
<prompt-ui-canvas id="canvas"></prompt-ui-canvas>
<script type="module">
import { PromptUIController } from "@rameshnaik/prompt-ui";
const fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const controller = new PromptUIController({
fields,
getData: () => data,
});
const bar = document.getElementById("bar");
bar.controller = controller;
bar.examplePrompts = ["Pie chart by department", "Sort by hours descending"];
const canvas = document.getElementById("canvas");
canvas.controller = controller;
canvas.data = data;
canvas.fields = fields;
</script>
</body>
</html>Framework Compatibility Summary
| Framework | Import | How properties are bound | Adapter needed? |
|---|---|---|---|
| React | @rameshnaik/prompt-ui/react | ref={promptUI.bar} | Built-in hook |
| Angular | @rameshnaik/prompt-ui | [controller]="ctrl" | No |
| Vue | @rameshnaik/prompt-ui | :controller="ctrl" | No |
| Svelte | @rameshnaik/prompt-ui | controller={ctrl} | No |
| Vanilla JS | @rameshnaik/prompt-ui | el.controller = ctrl | No |
Features
- Charts:
"pie chart by department","bar chart of hours with count" - Tables:
"show only Sales employees","sort by hours descending" - List-Detail:
"side panel view with title as Name" - Layout Transform:
"convert to table view","change this to side panel view"— replaces existing page content with the new layout - Theme:
"apply dark theme","revert dark theme" - Widget Management:
"undo last widget","clear all" - Voice Input: Built-in microphone button using Web Speech API
- PDF Export: Download any generated widget as PDF
- Prompt History: Arrow-up/down to navigate past prompts
Parser Modes
The library supports three parser modes for interpreting natural-language prompts. Choose the one that fits your needs:
| Mode | Strategy | Import |
|---|---|---|
| Mode 1 | Rule-based only — instant, free, offline | @rameshnaik/prompt-ui |
| Mode 2 | LLM-first → rule-based fallback — max coverage | @rameshnaik/prompt-ui/llm |
| Mode 3 | Rule-based first → LLM fallback — min cost, max speed | @rameshnaik/prompt-ui/llm |
Mode 1: Rule-Based Only (Default)
Instant, free, offline. No additional setup required.
The built-in parser uses pattern matching, stemming, fuzzy field resolution, and Levenshtein distance to interpret prompts. It handles common visualization patterns out of the box.
User prompt ──→ Rule-based parser ──→ VizSpec? ──→ Render
│
null (unrecognized)
│
Show error messageUsage:
import { PromptUIController } from "@rameshnaik/prompt-ui";
const controller = new PromptUIController({ fields });
// That's it — rule-based parser is used by defaultBest for: Internal tools, offline apps, low-complexity datasets, or when you don't want any external API dependency.
Mode 2: LLM-First with Rule-Based Fallback
Maximum coverage. The LLM handles every prompt. If the LLM call fails (network error, timeout, unparseable response), the rule-based parser is used as a safety net.
User prompt ──→ LLM parser ──→ valid VizSpec? ──→ Render
│ │
failed/timeout invalid JSON
│ │
└────────┬────────┘
↓
Rule-based parser (fallback) ──→ Render or errorUsage:
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createLLMParser } from "@rameshnaik/prompt-ui/llm";
const parser = createLLMParser({
call: async (systemPrompt, userPrompt) => {
// You provide the HTTP call — use ANY LLM provider
const res = await fetch("/api/llm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const data = await res.json();
return data.text; // must return the raw LLM text response
},
fallback: "rule-based", // "rule-based" (default) | "none"
});
const controller = new PromptUIController({ fields, parser });Options:
| Option | Type | Default | Description |
|---|---|---|---|
| call | LLMCallFn | required | Your LLM transport function |
| fallback | "rule-based" \| "none" | "rule-based" | What to do when LLM fails |
Best for: Consumer-facing apps where you want the broadest possible prompt coverage and can afford the latency/cost of LLM calls.
Mode 3: Hybrid — Rule-Based First, LLM Fallback
Minimum cost, maximum speed. Tries the instant rule-based parser first. Only calls the LLM when the rule-based parser returns null (unrecognized prompt). This keeps ~80% of prompts instant and free, while the LLM covers the remaining complex/ambiguous ones.
User prompt ──→ Rule-based parser ──→ VizSpec found? ──→ Render (instant)
│
null (can't parse)
│
LLM parser (async)
│
valid VizSpec? ──→ Render
│
null ──→ Show errorUsage:
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
const parser = createHybridParser({
call: async (systemPrompt, userPrompt) => {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
],
temperature: 0,
}),
});
const data = await res.json();
return data.choices[0].message.content;
},
});
const controller = new PromptUIController({ fields, parser });Best for: Production apps that want the best balance of speed, cost, and coverage. Most prompts resolve instantly via rules; complex ones gracefully upgrade to LLM.
Mode Comparison
| | Mode 1: Rule-Based | Mode 2: LLM-First | Mode 3: Hybrid |
|---|---|---|---|
| Latency | Instant (~0ms) | ~1-3s per prompt | Instant for known patterns, ~1-3s for complex |
| Cost | Free | Every prompt costs tokens | Only unrecognized prompts cost tokens |
| Offline | Yes | No (falls back to rules) | Degrades gracefully to rule-based |
| Coverage | Common patterns | Handles anything | Best of both |
| Setup | None | Provide call function | Provide call function |
| Complex prompts | May fail | Works | Works |
The call Function
Both createLLMParser and createHybridParser accept a call function with this signature:
type LLMCallFn = (systemPrompt: string, userPrompt: string) => Promise<string>;systemPrompt— The library generates this automatically from yourfields. It describes the available data schema and the expected JSON output format. You never need to write this yourself.userPrompt— The raw text the user typed (e.g."pie chart by department").- Return value — The raw text response from the LLM. The library validates and parses it internally.
The library never touches API keys, never bundles provider SDKs, and works with any LLM backend. You own the transport.
Provider Examples
Below are call function implementations for popular LLM providers. Pick the one that matches your setup:
OpenAI (direct):
const call = async (systemPrompt: string, userPrompt: string) => {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
],
temperature: 0,
}),
});
const data = await res.json();
return data.choices[0].message.content;
};Anthropic (direct):
const call = async (systemPrompt: string, userPrompt: string) => {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: "user", content: userPrompt }],
}),
});
const data = await res.json();
return data.content[0].text;
};Your own backend proxy (recommended for production):
const call = async (systemPrompt: string, userPrompt: string) => {
const res = await fetch("/api/prompt-ui/parse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const data = await res.json();
return data.text;
};Security tip: Never expose API keys in frontend code. Use a backend proxy endpoint that holds the key server-side.
Ollama (local):
const call = async (systemPrompt: string, userPrompt: string) => {
const res = await fetch("http://localhost:11434/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "llama3",
stream: false,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
],
}),
});
const data = await res.json();
return data.message.content;
};Loading State
When using Mode 2 or Mode 3 with an async LLM call, the prompt bar automatically:
- Shows a "Thinking..." spinner with a loading animation
- Disables the input field and buttons to prevent duplicate submissions
- Displays "Generating..." on the submit button
The controller exposes isLoading (boolean) and fires state-change events during loading transitions, so you can build custom loading UI if needed.
Complete LLM Integration Examples
Below are full working examples showing how to integrate LLM parsing in each supported framework. All examples use Mode 3 (hybrid) — swap createHybridParser for createLLMParser if you prefer Mode 2.
React + LLM
import { usePromptUI } from "@rameshnaik/prompt-ui/react";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
const FIELDS: FieldDescriptor[] = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const DATA = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
{ name: "Carol", department: "Sales", hours: 38 },
];
const parser = createHybridParser({
call: async (systemPrompt, userPrompt) => {
const res = await fetch("/api/prompt-ui/parse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const data = await res.json();
return data.text;
},
getData: () => DATA,
});
export default function TimecardPage() {
const promptUI = usePromptUI({ fields: FIELDS, parser });
return (
<>
<prompt-ui-bar
ref={promptUI.bar}
examplePrompts={["Pie chart by department", "Who works the most?"]}
/>
<prompt-ui-canvas
ref={promptUI.canvas}
data={DATA as unknown as Record<string, unknown>[]}
fields={FIELDS}
/>
</>
);
}Angular + LLM
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import "@rameshnaik/prompt-ui";
@Component({
selector: "app-timecard",
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<prompt-ui-bar
[controller]="controller"
[examplePrompts]="examples"
></prompt-ui-bar>
<prompt-ui-canvas
[controller]="controller"
[data]="data"
[fields]="fields"
></prompt-ui-canvas>
`,
})
export class TimecardComponent {
fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
examples = ["Pie chart by department", "Compare hours across departments"];
parser = createHybridParser({
call: async (systemPrompt, userPrompt) => {
const res = await fetch("/api/prompt-ui/parse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const data = await res.json();
return data.text;
},
getData: () => this.data,
});
controller = new PromptUIController({
fields: this.fields,
parser: this.parser,
});
}Vue + LLM
<script setup lang="ts">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";
const fields: FieldDescriptor[] = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const parser = createHybridParser({
call: async (systemPrompt, userPrompt) => {
const res = await fetch("/api/prompt-ui/parse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const json = await res.json();
return json.text;
},
getData: () => data,
});
const controller = new PromptUIController({ fields, parser });
</script>
<template>
<prompt-ui-bar
:controller="controller"
:examplePrompts="['Pie chart by department', 'Analyze hours trends']"
/>
<prompt-ui-canvas
:controller="controller"
:data="data"
:fields="fields"
/>
</template>Vanilla JS + LLM
<script type="module">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
const fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
];
const parser = createHybridParser({
call: async (systemPrompt, userPrompt) => {
const res = await fetch("/api/prompt-ui/parse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
});
const json = await res.json();
return json.text;
},
getData: () => data,
});
const controller = new PromptUIController({ fields, parser, getData: () => data });
document.getElementById("bar").controller = controller;
document.getElementById("canvas").controller = controller;
document.getElementById("canvas").data = data;
document.getElementById("canvas").fields = fields;
</script>The getData Option (Data-Aware LLM)
The createHybridParser accepts an optional getData function that provides the current dataset to the system prompt. When supplied, the library includes a dataset summary (row count, unique values, min/max/avg for numeric fields) in the system prompt sent to the LLM. This helps the LLM make smarter decisions about which chart types, groupings, and filters best fit your actual data.
const parser = createHybridParser({
call: myLLMCall,
getData: () => fetchedData, // provide live data
});Without getData, the LLM only sees the field schema. With it, the LLM also sees:
Dataset summary (150 rows):
- Name: 45 unique values, top: "Alice"(12), "Bob"(8), "Carol"(7)
- Department: 4 unique values, top: "Sales"(52), "Engineering"(41), "HR"(32)
- Hours: min=20, max=60, avg=38.5This enables prompts like "analyze this data" or "find insights" to produce meaningful results.
Backend Proxy Example (Node.js/Express)
In production, never expose API keys in frontend code. Here's a minimal backend proxy:
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/prompt-ui/parse", async (req, res) => {
const { system, user } = req.body;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: system },
{ role: "user", content: user },
],
temperature: 0,
}),
});
const data = await response.json();
res.json({ text: data.choices[0].message.content });
});
app.listen(3001);Advanced: Using Exported Utilities
The /llm sub-entrypoint exports two utility functions for building custom parser pipelines:
import { buildSystemPrompt, validateAndParse } from "@rameshnaik/prompt-ui/llm";buildSystemPrompt(fields, data?) — Generates the system prompt string that describes your data schema and expected JSON output format. Use this to inspect what the library sends to the LLM, or to build a completely custom parser.
import { buildSystemPrompt } from "@rameshnaik/prompt-ui/llm";
const prompt = buildSystemPrompt(fields, data);
console.log(prompt); // see exactly what the LLM receivesvalidateAndParse(raw, fields) — Takes a raw LLM text response and validates/parses it into an ActionResult. Returns null if the response is malformed. Use this to integrate your own LLM call logic while reusing the library's validation.
import { validateAndParse } from "@rameshnaik/prompt-ui/llm";
const raw = await myCustomLLMCall(prompt);
const result = validateAndParse(raw, fields);
if (result) {
// valid VizSpec, ThemeCommand, etc.
}Recommended Models
The library works with any LLM that can return valid JSON. Recommended options:
| Model | Provider | Notes |
|---|---|---|
| gpt-4o-mini | OpenAI | Best cost/quality ratio for structured JSON |
| gpt-4o | OpenAI | Higher accuracy for complex prompts |
| claude-sonnet-4-20250514 | Anthropic | Excellent JSON adherence |
| llama3 | Ollama (local) | Free, offline, good for development |
| gemini-2.0-flash | Google | Fast and cost-effective |
Tip: Use
temperature: 0for deterministic, consistent results. The library's system prompt is optimized for JSON output — most models follow it reliably.
API Reference
PromptUIController
The central state manager. Create one instance per page and share it between the bar and canvas.
const controller = new PromptUIController(options);| Option | Type | Default | Description |
|---|---|---|---|
| fields | FieldDescriptor[] | required | Describes available data columns |
| getData | () => Record<string, unknown>[] | — | Returns current data at prompt time |
| parser | ParserFn | built-in rule-based | Custom parser function (sync or async) |
| maxWidgets | number | 6 | Max stacked widgets |
| maxHistory | number | 50 | Max prompt history entries |
Methods:
| Method | Description |
|---|---|
| submitPrompt(text) | Parse and execute a prompt (returns Promise<void> — async for LLM parsers) |
| confirmAction() | Confirm a pending theme change |
| cancelAction() | Cancel a pending theme change |
| dismissWidget(id) | Remove a specific widget |
| undoLast() | Remove the most recent widget |
| clearAll() | Remove all widgets |
| revertTransform() | Revert the active layout transform and restore original content |
| navigateHistory(dir) | Navigate prompt history ("up" or "down") |
Properties (read-only):
| Property | Type | Description |
|---|---|---|
| widgets | ActiveWidget[] | Currently active widgets |
| history | string[] | Past prompts |
| error | string \| null | Current error message |
| toast | PromptToast \| null | Current toast notification |
| pendingConfirmation | PendingConfirmation \| null | Pending theme confirmation |
| activeTransform | ActiveTransform \| null | Currently active layout transform |
| isActive | boolean | Whether any widgets are shown |
| isLoading | boolean | Whether an async parser call is in progress |
Events:
| Event | Description |
|---|---|
| state-change | Fired whenever any state property changes (including isLoading transitions) |
<prompt-ui-bar>
The prompt input bar with voice input, undo, clear, and generate controls.
| Property | Type | Default | Description |
|---|---|---|---|
| controller | PromptUIController | required | The controller instance |
| placeholder | string | "Describe what you want to see…" | Input placeholder text |
| examplePrompts | string[] | [] | Example chips shown below the bar |
<prompt-ui-canvas>
Renders the generated widgets (charts, tables, list-detail views) and handles layout transforms.
| Property | Type | Default | Description |
|---|---|---|---|
| controller | PromptUIController | required | The controller instance |
| data | Record<string, unknown>[] | [] | The dataset to visualize |
| fields | FieldDescriptor[] | [] | Field descriptors for the data |
| contentTarget | string | "" | CSS selector for existing page content to hide when a layout transform is active |
usePromptUI (React only)
import { usePromptUI } from "@rameshnaik/prompt-ui/react";
const promptUI = usePromptUI({ fields });
// promptUI.controller — the PromptUIController instance
// promptUI.bar — ref callback for <prompt-ui-bar>
// promptUI.canvas — ref callback for <prompt-ui-canvas>Layout Transform
The layout transform feature lets users convert existing page content into a different layout using natural language. Unlike widgets (which stack on top of your page), a layout transform replaces the existing content and provides a "Revert to original" button.
The content-target Attribute
The content-target attribute (or contentTarget JS property) accepts a CSS selector that identifies which element(s) on your page should be hidden when a layout transform is active. This is the bridge between the library and your existing page content.
| Attribute (HTML) | Property (JS) | Type | Default |
|---|---|---|---|
| content-target | contentTarget | string (CSS selector) | "" (empty — no content is hidden) |
Key behaviors:
- The selector is resolved via
document.querySelectorAll(), so it can match one or multiple elements - Matched elements are hidden by setting
display: none— their DOM position is preserved - When the transform is reverted, the original
displayvalue is restored exactly as it was - If
content-targetis empty or not set, layout transforms still render in the canvas but nothing else on the page is hidden - The canvas is cleaned up properly on
disconnectedCallback— navigating away automatically restores hidden elements
How it works
- Set the
content-targetattribute on<prompt-ui-canvas>to a CSS selector identifying the existing content to replace:
<prompt-ui-canvas id="canvas" content-target="#my-list"></prompt-ui-canvas>
<ul id="my-list">
<!-- your existing list — hidden during transforms -->
</ul>When the user types a transform prompt (e.g.
"convert to table view"), the library:- Queries all elements matching the
content-targetselector - Saves their current
displaystyle and setsdisplay: none - Renders the new layout (table, list-detail, or chart) inside the canvas
- Shows a "Revert to original" button in the header
- Queries all elements matching the
Clicking "Revert to original" (or calling
controller.revertTransform()) restores the original elements' display values.
Selector Patterns
<!-- Single element by ID -->
<prompt-ui-canvas content-target="#employee-list"></prompt-ui-canvas>
<!-- Multiple elements by class -->
<prompt-ui-canvas content-target=".data-section"></prompt-ui-canvas>
<!-- Complex selector — hides both the list and its header -->
<prompt-ui-canvas content-target="#list-header, #list-body"></prompt-ui-canvas>
<!-- Attribute selector -->
<prompt-ui-canvas content-target="[data-replaceable]"></prompt-ui-canvas>Supported prompts
| Prompt example | Result |
|---|---|
| "convert to table view" | Replaces content with a data table |
| "change this to side panel view" | Replaces content with a list-detail panel |
| "switch to pie chart" | Replaces content with a pie chart |
| "transform to bar chart view" | Replaces content with a bar chart |
| "show data as grid" | Replaces content with a table grid |
Framework examples
React:
<prompt-ui-canvas
ref={promptUI.canvas}
data={data}
fields={fields}
contentTarget="#employee-list"
/>Angular:
<prompt-ui-canvas
[controller]="controller"
[data]="data"
[fields]="fields"
contentTarget="#employee-list"
></prompt-ui-canvas>Vue:
<prompt-ui-canvas
:controller="controller"
:data="data"
:fields="fields"
content-target="#employee-list"
/>Svelte:
<prompt-ui-canvas
controller={controller}
{data}
{fields}
content-target="#employee-list"
/>Vanilla JS:
const canvas = document.getElementById("canvas");
canvas.contentTarget = "#employee-list";
// or via HTML attribute:
// <prompt-ui-canvas content-target="#employee-list"></prompt-ui-canvas>Full Page Example
Here's a complete example showing content-target in a real layout:
<prompt-ui-bar id="bar"></prompt-ui-bar>
<prompt-ui-canvas id="canvas" content-target="#original-content"></prompt-ui-canvas>
<div id="original-content">
<h2>Employee Directory</h2>
<ul>
<li>Alice — Sales — 40h</li>
<li>Bob — Engineering — 45h</li>
<li>Carol — Sales — 38h</li>
</ul>
</div>
<script type="module">
import { PromptUIController } from "@rameshnaik/prompt-ui";
const fields = [
{ key: "name", label: "Name", type: "string" },
{ key: "department", label: "Department", type: "string" },
{ key: "hours", label: "Hours", type: "number" },
];
const data = [
{ name: "Alice", department: "Sales", hours: 40 },
{ name: "Bob", department: "Engineering", hours: 45 },
{ name: "Carol", department: "Sales", hours: 38 },
];
const controller = new PromptUIController({ fields, getData: () => data });
const bar = document.getElementById("bar");
bar.controller = controller;
bar.examplePrompts = ["Convert to table view", "Show as side panel"];
const canvas = document.getElementById("canvas");
canvas.controller = controller;
canvas.data = data;
canvas.fields = fields;
</script>When the user types "convert to table view", the #original-content div is hidden and a generated data table appears in the canvas. Clicking "Revert to original" brings back the directory list.
Programmatic Revert
You can revert a layout transform from code at any time:
controller.revertTransform();This restores all hidden elements and removes the generated transform from the canvas.
License
MIT
