untitled-bridge
v3.2.0
Published
Plugin-based builder bridge with live design token editing, smart element selection, source tracking, and two-way live editing support
Maintainers
Readme
untitled-bridge
A plugin-based builder bridge SDK that enables live design token editing, element selection, source tracking, and two-way live editing between preview iframes and parent builder applications.
Features
- 🎨 Live Design Token Editing: Figma-like live CSS variable updates via postMessage
- 🔌 Plugin Architecture: Extensible message routing with namespaced plugins
- 🎯 Smart Element Selection: Interactive/content elements only (excludes structural divs)
- 🔒 Session Tokens: Built-in token validation for secure handshakes
- 📡 Event Streaming: Hover, click, scroll events with element metadata
- 🌓 Theme Toggle: Runtime dark/light mode switching
- 🎯 Source Tracking: Automatic element identification with unique IDs and source file mapping
- ✏️ Two-Way Live Editing: Inline text and image editing with real-time sync
- 🖼️ Image Editing: Right-click to edit image sources with instant updates
Element Selection Behavior
Selectable Elements ✅
- Interactive:
a,button,input,textarea,select,form - Text:
h1,h2,h3,h4,h5,h6,p,span,label,strong,em,li - Media:
img - ARIA:
[role="button"]
Excluded Elements ❌
- Structural/Layout:
main,section,div,header,footer,nav,aside,article - Meta:
html,body,head,script,style,meta,link,title - Hidden/tiny elements (width < 1px or height < 1px)
Install (from npm)
npm install untitled-bridgeUse in a web app (Next.js/App Router or Pages Router)
Add a side-effect import at your root entry so it runs on the client:
// app/layout.tsx or pages/_app.tsx
import 'untitled-bridge';When the page loads in the browser, you'll see:
[untitled-bridge] v3.0.0 - Plugin Architecture Enabled
[untitled-bridge] DesignSync plugin initializedLive Design Token Editing
The bridge includes a built-in DesignSync plugin that enables Figma-like live editing of CSS variables.
From Parent App: Apply Token Changes
// Send live token update to iframe
iframe.contentWindow?.postMessage({
protocol: "untitled-bridge",
version: "0.3.0",
type: "designTokens/applyPatch",
token: sessionToken,
patch: {
"color.primary": {
base: "oklch(0.7 0.2 265)", // Light mode
modes: { dark: "oklch(0.85 0.15 195)" } // Dark mode
},
"color.background": {
base: "oklch(1 0 0)"
},
"radius": {
base: "0.5rem"
}
}
}, iframeOrigin);Supported Tokens
| Token Key | CSS Variable | Description |
|-----------|--------------|-------------|
| color.background | --background | Page background |
| color.foreground | --foreground | Main text color |
| color.primary | --primary | Primary brand color |
| color.primary.fg | --primary-foreground | Text on primary |
| color.card.bg | --card | Card background |
| color.muted | --muted | Muted background |
| color.border | --border | Border color |
| color.ring | --ring | Focus ring |
| radius | --radius | Border radius |
Toggle Theme Mode
// Switch to dark mode
iframe.contentWindow?.postMessage({
protocol: "untitled-bridge",
version: "0.3.0",
type: "ui/setThemeMode",
token: sessionToken,
mode: "dark" // or "light"
}, iframeOrigin);Request Current Token Snapshot
// Request current CSS variable values (for debugging/inspect)
const requestId = crypto.randomUUID();
iframe.contentWindow?.postMessage({
protocol: "untitled-bridge",
version: "0.3.0",
type: "designTokens/requestSnapshot",
token: sessionToken,
requestId: requestId
}, iframeOrigin);
// Listen for response
window.addEventListener("message", (e) => {
if (e.data.type === "designTokens/snapshot" && e.data.requestId === requestId) {
console.log("Current tokens:", e.data.snapshot);
}
});Plugin Architecture
The bridge exposes a global window.UntitledBridge object with plugin registration capabilities.
Creating a Custom Plugin
const myPlugin = {
init: (ctx) => {
// Called once when plugin is registered
// ctx: { post, state, getCssVar, setCssVar }
console.log("Plugin initialized with token:", ctx.state.token);
},
onMessage: (msg, ctx) => {
// Called when matching namespace message arrives
// msg.type format: "namespace/action"
if (msg.type === "myPlugin/doSomething") {
const currentBg = ctx.getCssVar("--background");
ctx.setCssVar("--my-custom-var", "red", ":root");
ctx.post({
protocol: "untitled-bridge",
type: "myPlugin/done",
token: ctx.state.token
});
}
}
};
// Register plugin (matches "myPlugin/*" messages)
window.UntitledBridge.registerPlugin("myPlugin", myPlugin);Plugin Context API
Plugins receive a context object with:
| Method | Description |
|--------|-------------|
| post(type, payload) | Send message to parent window |
| getCssVar(name) | Read CSS variable from :root |
| setCssVar(name, value, scope) | Write CSS variable ( :root or scoped) |
| state.token | Current session token |
| state.allowedOrigin | Allowed postMessage origin |
CLI
After installing (or via npx once published):
npx untitled-bridgePrints:
[untitled-bridge] Hello World (CLI)Local development
# run the module directly
node index.js
# run the CLI directly
node bin/untitled-bridge.js
# or pack a tarball for testing in another app
npm pack
# then in another app
npm install ../untitled-bridge-1.1.0.tgzTwo-Way Live Editing
The bridge now supports seamless two-way editing between visual preview and source code:
Text Editing
- Click any text element in edit mode to start inline editing
- Press Enter to save, Escape to cancel
- Changes are instantly reflected via HMR and saved to source files
Image Editing
- Right-click any image in edit mode to edit its source URL
- Changes are applied immediately with instant visual feedback
Source Tracking
- Each element gets a unique
data-ub-idattribute for tracking - Source file mapping via
data-source-fileattributes - Enables precise code-to-visual element correlation
Edit Mode Messages
// Enable edit mode
iframe.contentWindow?.postMessage({
protocol: "untitled-bridge",
version: "0.3.0",
type: "setEditMode",
enabled: true
}, iframeOrigin);
// Text edit event
{
protocol: "untitled-bridge",
version: "0.3.0",
type: "textEdit",
ubId: "ub-1234567890-0",
sourceFile: "src/App.jsx",
elementSelector: "h1.welcome",
newText: "Updated heading",
originalText: "Welcome"
}
// Image edit event
{
protocol: "untitled-bridge",
version: "0.3.0",
type: "imageEdit",
ubId: "ub-1234567890-1",
sourceFile: "src/App.jsx",
newSrc: "https://example.com/new-image.jpg",
originalSrc: "https://example.com/old-image.jpg"
}Protocol Version
Current: 0.3.0
All messages use the format:
{
protocol: "untitled-bridge",
version: "0.3.0",
type: "namespace/action",
token: string,
...payload
}Version History
| Version | Changes | |---------|---------| | 3.2.0 | Two-way live editing, source tracking, image editing, enhanced messaging | | 3.1.4 | Plugin architecture, live design token editing, theme toggle | | 2.1.1 | Smart element filtering (exclude divs/sections/main) | | 2.0.0 | Enhanced selection + metadata | | 1.x | Basic bridge + hello handshake |
Notes
- The package logs on import; keep the
import 'untitled-bridge'in a client-executed file. - All design token updates apply immediately without page reload
- Supports both light and dark mode variants via
modesin token patches - CSS variable scoping uses
data-themeattribute for theme switching - Set executable bit on the CLI if needed locally:
chmod +x bin/untitled-bridge.js
