Skip to content
/ Widgets Public

A compact, schema-driven open source alternative to OpenAI ChatKit widgets.

License

Notifications You must be signed in to change notification settings

Gan-Tu/Widgets

Repository files navigation

WidgetRenderer

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

What’s in this repo

  • 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).

Install (for use in your app)

npm install @tugan/widgets

Import the styles once in your app entry:

import "@tugan/widgets/styles.css";

Run locally

npm install
npm run dev

Open the URL printed by Vite, then visit /gallery, /docs, or /playground.

Basic usage (embed in your app)

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);
      }}
    />
  );
}

WidgetRenderer props

  • 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 schema
  • onAction?: (action, formData?) => void: receives declarative actions (and optional captured form state)
  • components?: ComponentRegistry: override / add components to the registry
  • theme?: "light" | "dark": force theme for the widget subtree
  • debug?: boolean: render validated data under the widget

Template rules (the important bits)

  • 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.

Where to look in code

  • 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

Extending the system

  1. Add a component under src/widget/components/*
  2. Register it in src/widget/registry.ts
  3. Add an example to src/examples/widgetExamples.ts (so it shows up in /gallery)

Notes

  • DatePicker is implemented as a styled native date input for simplicity (matching the Widget UI API surface).

About

A compact, schema-driven open source alternative to OpenAI ChatKit widgets.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages