A compact, schema-driven widget renderer for chat UIs. Pass a Widget UI template string + a Zod schema + data, and it renders a small, opinionated widget with minimal interactivity.
DeepWiki Docs: https://deepwiki.com/Gan-Tu/Widgets
CleanShot.2025-12-30.at.03.15.47.mp4
- Reusable renderer:
WidgetRenderer(published as@tugan/widgets) - Component library: the Widget UI primitives (containers, layout, text, content, forms)
- Demo app:
/gallery— lots of pre-built widgets/docs— component docs + reference/playground— live template + JSON editing
Built with React, Tailwind v4, shadcn/ui, and Motion (motion/react).
npm install @tugan/widgetsImport the styles once in your app entry:
import "@tugan/widgets/styles.css";npm install
npm run devOpen the URL printed by Vite, then visit /gallery, /docs, or /playground.
import "@tugan/widgets/styles.css";
import { WidgetRenderer } from "@tugan/widgets";
import WidgetSchema from "./schema";
export function WidgetMessage() {
return (
<WidgetRenderer
template={templateString}
schema={WidgetSchema}
data={widgetData}
onAction={(action, formData) => {
console.log("action", action);
console.log("formData", formData);
}}
/>
);
}template: string: Widget UI template (a strict JSX-like language)schema: z.ZodTypeAny: Zod schema for widget data (validated before render)data: z.infer<typeof schema>: data matching the schemaonAction?: (action, formData?) => void: receives declarative actions (and optional captured form state)components?: ComponentRegistry: override / add components to the registrytheme?: "light" | "dark": force theme for the widget subtreedebug?: boolean: render validated data under the widget
- No text children: text is always passed via props.
// ✅ valid
<Text value="Hello" />
<Button label="Continue" />
// ❌ invalid
<Text>Hello</Text>
<Button>Continue</Button>- Declarative logic only: bindings (
{title}), conditions ({ok ? <Badge ... /> : null}), and.map(...)loops. - No arbitrary JS: the template engine is intentionally conservative for safety and predictability.
- Renderer:
src/widget/WidgetRenderer.tsx - Template engine:
src/widget/renderer/templateEngine.tsx - Widget components:
src/widget/components/* - Registry:
src/widget/registry.ts - Example widgets:
src/examples/widgetExamples.ts - Demo routes:
src/pages/*+src/App.tsx
- Add a component under
src/widget/components/* - Register it in
src/widget/registry.ts - Add an example to
src/examples/widgetExamples.ts(so it shows up in/gallery)
DatePickeris implemented as a styled native date input for simplicity (matching the Widget UI API surface).