Widget System Architecture
How widgets work under the hood — the Blueprint protocol, expression engine, and multi-platform rendering.
Overview
Every widget in Widge is powered by the Blueprint protocol — a platform-agnostic JSON format that describes what to render, not how. The server sends a Blueprint, and each client (Web, Mobile, TV, CLI) interprets it using its own renderer.
Server (Blueprint) ──► Web (Tamagui renderer)
──► Mobile (React Native renderer)
──► TV (Focus-aware renderer)
──► Embed (DOM renderer)
──► CLI (ASCII renderer)Blueprint Response
Every widget request returns a BlueprintResponse:
{
"meta": {
"id": "wdg_abc123",
"title": "Revenue",
"update_policy": { "mode": "poll", "ttl": 30 }
},
"state": {
"selectedPeriod": "week"
},
"root": {
"type": "Container",
"id": "root",
"props": { "direction": "column", "gap": 8 },
"children": [
{
"type": "Text",
"id": "title",
"props": { "content": "${config.title}", "variant": "h2" }
},
{
"type": "Chart",
"id": "chart",
"props": {
"chartType": "line",
"data": "${config.data}"
}
}
]
}
}Meta
| Field | Description |
|---|---|
id | Unique widget instance ID |
title | Display title |
update_policy.mode | poll (client refreshes) or push (server pushes) |
update_policy.ttl | Seconds between refreshes |
designSize | Optional base dimensions for scaling (default 624×400) |
State
Initial local state for the widget. Updated via set_state actions without server round-trips.
Root
The UI tree — a recursive structure of Blueprint Nodes.
Node Types
| Type | Description | Key Props |
|---|---|---|
| Container | Flex layout | direction, alignMain, alignCross, gap |
| Text | Rich text with markdown and expressions | content, variant, maxLines, align |
| Image | Remote image with fallback | url, fallbackIcon, contentMode |
| Icon | SF Symbol or Material icon | name, size, color |
| Chart | 20+ chart types | chartType, data, color, showAxes |
| Table | Data table | columns, data, showHeader |
| Number | Formatted numeric display | value, format, prefix, suffix |
| Clock | Analog or digital clock | clockType, timezone, showSeconds |
| Weather | Weather display | temperature, city, unit, weatherCode |
| Spacer | Layout spacing | size |
| Lottie | Animation playback | url, loop, autoplay |
Chart Types
Line, Bar, Pie, Donut, Area, Gauge, Horizontal Bar, Stacked Bar, Scatter, Bubble, Radar, Heatmap, Treemap, Funnel, Waterfall, Candlestick, Histogram, Progress Bar, Progress Ring, Goal Tracker.
Expression Engine
Blueprint props support expression bindings — a safe, sandboxed language evaluated at render time. Wrap expressions in dollar-brace syntax inside any string prop.
Basics
${config.title} → property access
${state.count + 1} → arithmetic
${state.active ? 'Yes' : 'No'} → ternary
${config.data.length} → array lengthSupported Operations
| Category | Examples |
|---|---|
| Arithmetic | +, -, *, /, % |
| Comparison | >, <, >=, <=, ===, !== |
| Logical | &&, ||, ! |
| Ternary | condition ? a : b |
| Array methods | .map(), .filter(), .find(), .slice(), .join() |
| String methods | .trim(), .toUpperCase(), .split(), .replace() |
| Math | Math.round(), Math.floor(), Math.max() |
| Utilities | parseInt(), String(), Number(), JSON.parse() |
Context Variables
| Variable | Description |
|---|---|
config | Widget configuration (title, data, colors, etc.) |
state | Current local state |
data | Data source records |
Security
The expression engine uses a recursive descent parser — no eval() or new Function(). Access to window, document, process, require, import, constructor, and prototype is blocked.
Actions
Nodes can respond to taps via the interactions.on_tap array:
{
"type": "Text",
"id": "counter",
"props": { "content": "Count: ${state.count}" },
"interactions": {
"on_tap": [
{ "type": "set_state", "payload": { "count": "${state.count + 1}" } }
]
}
}Action Types
| Action | Description |
|---|---|
set_state | Update local widget state |
mutation | Send an intent to the server (with optional optimistic UI) |
open_url | Open a URL in browser or deep link |
copy_clipboard | Copy text to clipboard |
present_actions | Show an action sheet |
api_call | Make an HTTP request |
run_animation | Trigger shake, pulse, fade |
haptic | Trigger haptic feedback (mobile) |
Conditional Actions
Every action supports an if expression:
{
"type": "set_state",
"if": "state.count < 10",
"payload": { "count": "${state.count + 1}" }
}Theme Tokens
Styles use semantic tokens that resolve to CSS variables (web) or hex values (native):
| Token | CSS Variable | Usage |
|---|---|---|
$widget_bg | --widget-bg | Widget background |
$widget_text | --widget-text | Primary text |
$widget_accent | --widget-accent | Accent color |
$widget_border | --widget-border | Border color |
$chart_color_1–$chart_color_5 | --chart-color-* | Chart series |
$text_primary | --color-text-primary | Primary text |
$text_secondary | --color-text-secondary | Secondary text |
$status_success | --color-status-success | Success state |
$status_error | --color-status-error | Error state |
Capabilities
When requesting a Blueprint, the client declares its capabilities:
{
"platform": "web",
"capabilities": {
"mutations": true,
"present_actions": true,
"optimistic_ui": true
}
}The server adapts the Blueprint based on what the client supports. A read-only embed might set mutations: false to receive a simpler tree without interactive actions.
Widget Definitions
Each widget type is defined by a Widget Definition containing:
- Config schema — JSON Schema describing the configuration fields
- Template — Blueprint tree with expression bindings
- Size constraints — min/max width and height in grid units
- Variants — layout variations for different sizes
When a user adds a widget to a dashboard, the server evaluates the template against the config and data source to produce the final Blueprint.
Responsive Grid
Widgets live on a responsive grid:
| Breakpoint | Columns | Row Height |
|---|---|---|
| Desktop (>1200px) | 12 | 100px |
| Tablet (>768px) | 8 | 80px |
| Mobile (<768px) | 2 | 60px |
| TV | 16 | 120px |
Widget positions: x (column), y (row), w (width in columns), h (height in rows).
Package Structure
The Blueprint protocol is published as @wdg/blueprint — a zero-dependency TypeScript package:
@wdg/blueprint
├── types — BlueprintNode, BlueprintResponse, BlueprintAction, ...
├── expressions — Safe expression engine (recursive descent parser)
├── template — Template processing and config defaults
└── tokens — Theme token resolution (CSS vars / native hex)Import it in any environment without pulling in UI framework dependencies:
import type { BlueprintResponse, BlueprintNode } from '@wdg/blueprint'
import { safeEvaluate, evaluateExpression } from '@wdg/blueprint'