A modern, high-performance desktop dialog library for Bun and Electrobun on macOS. Replace outdated system prompts and heavy UI toolkits with polished, animated HTML5 dialogs powered by a native macOS WebView.
- Architecture
- Project Structure
- Getting Started
- Usage Guide
- Dialog API Reference
- Execution Modes
- Examples
Electrobun compiles and packages a Bun process alongside a native macOS Cocoa shell container.
| Layer | Role |
|---|---|
| Bun Process (Main) | Manages dialog lifecycle, collects values, executes subprocesses via Bun.spawn, and coordinates RPC transactions |
| WebView (Renderer) | Renders modernized HTML5 layouts via protocol resolution (views://) using low-overhead IPC |
electrobun_easydialogs/
├── src/
│ ├── bun/
│ │ ├── index.ts # CLI entrypoint & demo runner (re-exports ElectrobunEasyDialogs)
│ │ └── electrobun_easydialogs.ts # Core ElectrobunEasyDialogs class — the single-file distributable
│ ├── mainview/
│ │ ├── index.html # WebView shell
│ │ ├── index.css # WebView styles
│ │ ├── index.ts # WebView TypeScript entrypoint
│ │ ├── dynamic-dialog.js # Runtime dialog renderer (compiled)
│ │ └── wrapper-module.js # UI module (compiled)
│ ├── shared-types.ts # Shared type definitions
│ └── types.d.ts # Ambient declarations
├── icons/ # Premium Sequoia-style 3D dialog icons
│ ├── success.png
│ ├── warning.png
│ ├── error.png
│ ├── info.png
│ ├── question.png
│ ├── settings.png
│ ├── security.png
│ ├── database.png
│ ├── network.png
│ └── user.png
├── demos/ # Individual dialog demo scripts
│ ├── calendar_dialog.ts
│ ├── comprehensive_form.ts
│ ├── custom_colors_demo.ts # Custom color configurations demo
│ ├── entry_dialog.ts
│ ├── error_dialog.ts
│ ├── feedback_form.ts
│ ├── icon_dialog_demo.ts # Custom and built-in icons demo
│ ├── info_dialog.ts
│ ├── login_form.ts
│ ├── password_dialog.ts
│ ├── profile_form.ts
│ ├── question_dialog.ts
│ ├── scale_dialog.ts
│ ├── secure_html_demo.ts # Secure HTML dialog and XSS protection demo
│ ├── server_settings_form.ts
│ ├── simple_form.ts
│ ├── theme_toggle_demo.ts # Light/Dark mode toggle demo
│ └── warning_dialog.ts
├── examples/ # Integration examples
│ ├── test_vanilla_bun.ts # Vanilla Bun (outside Electrobun) integration test
│ └── zsh_example.sh # Full zsh CLI integration showcase
├── screenshots/ # Dialog layout screenshots for documentation
│ ├── login_dialog.png
│ └── server_settings.png
├── electrobun_easydialogs # zsh CLI wrapper script (executable)
├── build.ts # Build script (compiles app + assembles dist/)
├── electrobun.config.ts # Electrobun configuration
├── package.json
├── tsconfig.json
└── bun.lock
Note:
build/,dist/,node_modules/, and the root-levelelectrobun_easydialogs.ts(a build artifact) are excluded from version control via.gitignore.
- Bun ≥ 1.0
- macOS (arm64 or x86_64)
bun installbun run devThis compiles the Electrobun app bundle into ./build and starts a live-reload watcher.
bun run buildOutputs:
dist/electrobun_easydialogs.ts— the self-contained, zero-dependency TypeScript moduledist/views/mainview/— compiled WebView assetsdist/electrobun-easydialogs-dev.app— the complete macOS app bundle
src/bun/electrobun_easydialogs.ts is a self-contained single file with zero local imports — copy it directly into any Bun project.
import ElectrobunEasyDialogs from "./electrobun_easydialogs"; // copy electrobun_easydialogs.ts into your project
const easydialogs = new ElectrobunEasyDialogs();
// Ask a yes/no question
const runBackup = await easydialogs.question("Run a backup database scan?", {
title: "Database Utility",
okLabel: "Run Backup",
cancelLabel: "Skip",
});
if (runBackup) {
// Spawn a subprocess via Bun
const proc = Bun.spawn(["tar", "-czf", "backup.tar.gz", "./src"]);
const output = await new Response(proc.stdout).text();
// Show output in an info dialog
await easydialogs.info(`Backup completed!\n\n${output}`);
}Run the interactive full demo:
bun run demoRun a specific dialog demo:
EASYDIALOGS_DEMO_FILE=demos/calendar_dialog.ts bun run dev
EASYDIALOGS_DEMO_FILE=demos/comprehensive_form.ts bun run dev
EASYDIALOGS_DEMO_FILE=demos/entry_dialog.ts bun run dev
EASYDIALOGS_DEMO_FILE=demos/password_dialog.ts bun run dev
EASYDIALOGS_DEMO_FILE=demos/scale_dialog.ts bun run devRun the vanilla Bun integration test:
bun run test:vanilla
# or directly:
bun run examples/test_vanilla_bun.tsForce-test the AppleScript (osascript) serverless fallback:
EASYDIALOGS_FORCE_OSASCRIPT=true bun run test:vanillaelectrobun_easydialogs in the project root is a native zsh CLI wrapper. It reads dialog type and options from flags, delegates to the compiled Electrobun app, and returns output via stdout / exit codes.
Run the full zsh showcase:
bun run example:zsh
# or directly:
zsh examples/zsh_example.shManual terminal commands:
# Info notification (auto-closes in 5s)
./electrobun_easydialogs --info --title="Build Warning" --text="Compile complete with 2 notices." --timeout=5
# Branching yes/no prompt
./electrobun_easydialogs --question --title="Restart Service?" --text="Apply configuration updates?"
if [ $? -eq 0 ]; then
echo "Restarting service..."
fi
# Capture text input
USERNAME=$(./electrobun_easydialogs --entry --title="Create Account" --text="Choose a system username:")
echo "Registering: $USERNAME"
# Multi-field form (pipe-separated output)
CONFIG=$(./electrobun_easydialogs --forms --title="Microservice Setup" \
--add-entry="Service Name" \
--add-password="Admin Token" \
--add-combo="Cache Provider" --combo-values="Redis,Memcached,None")
# e.g. output: "my-api|secret_token|Redis"This reference acts as a complete cheatsheet for all options, method signatures, return types, and CLI flags.
These options apply to all dialog methods (in TypeScript as CommonOptions) and can be specified via corresponding CLI flags/environment variables.
| TypeScript Option | CLI Flag / Env Var | Type | Description |
|---|---|---|---|
title |
--title |
string |
The title displayed in the native dialog title bar. |
width |
(Auto-calculated) | number |
The width of the dialog window in pixels (default: 460 for messages, 560 for forms, 600 for HTML). |
height |
(Auto-calculated) | number |
The height of the dialog window in pixels. |
timeout |
--timeout |
number |
Auto-closes the dialog after N seconds (returns null/false/exit status 1). |
okLabel |
--ok-label |
string |
Text for the confirmation or submit button (e.g. "OK", "Submit", "Yes"). |
cancelLabel |
--cancel-label |
string |
Text for the abort button (e.g. "Cancel", "No", "Abort"). |
extraButton |
--add-button |
string |
Text for an optional third button. Pressing it returns 'extra' / exit code 2 (Forms only). |
icon |
--icon |
string |
Built-in icon name ('info', 'warning', 'error', 'danger', 'question', 'none') or absolute path to a custom .png/.svg. |
theme |
--theme |
'light' | 'dark' | 'system' |
Force the dialog rendering theme mode (default: 'system'). |
colors |
--colors |
Record<string, string> |
Dynamic styling overrides (as a JSON string in CLI, e.g. '{"bg":"#1c1c1e","primary":"#af52de"}'). Supported keys: bg (background), surface, text, primary (accent color), border. |
modalHint |
- | boolean |
Hints/renders the window in a modal state relative to parent or desktop. |
attachParent |
- | number |
Window handle/ID of the parent window to attach the dialog to. |
| |---
Displays an informational alert with a single button.
- TypeScript Signature:
async easydialogs.info(message: string, options?: InfoOptions): Promise<void>
- InfoOptions:
noWrap?: boolean— Prevents text wrappingnoMarkup?: boolean— Disables parsing basic HTML/markup tagsellipsize?: boolean— Truncates text with ellipses if it exceeds line boundsiconName?: string— Legacy/alternative icon identifier string
- Examples:
// TypeScript await easydialogs.info("Configuration loaded successfully!", { title: "System Info", icon: "success" });
# CLI ./electrobun_easydialogs --info --title="System Info" --text="Configuration loaded successfully!" --icon="success"
Displays a cautionary alert window.
- TypeScript Signature:
async easydialogs.warning(message: string, options?: WarningOptions): Promise<void>
- WarningOptions: Inherits all
InfoOptions. - Examples:
// TypeScript await easydialogs.warning("Disk space is exceeding 90%.", { title: "Disk Space Warning" });
# CLI ./electrobun_easydialogs --warning --title="Disk Space Warning" --text="Disk space is exceeding 90%."
Displays a critical failure or error block.
- TypeScript Signature:
async easydialogs.error(message: string, options?: ErrorOptions): Promise<void>
- ErrorOptions: Inherits all
InfoOptions. - Examples:
// TypeScript await easydialogs.error("Database connection refused.", { title: "Database Error" });
# CLI ./electrobun_easydialogs --error --title="Database Error" --text="Database connection refused."
Displays a branching confirmation prompt with two options.
- TypeScript Signature:
async easydialogs.question(message: string, options?: QuestionOptions): Promise<boolean>
- QuestionOptions:
defaultCancel?: boolean— Sets the default focused button to Cancel instead of OKnoWrap?: boolean,noMarkup?: boolean,ellipsize?: boolean
- Returns:
Promise<boolean>(truefor OK/Yes,falsefor Cancel/No/dismissal) - Examples:
// TypeScript const confirm = await easydialogs.question("Apply pending updates now?", { title: "System Update", okLabel: "Update", cancelLabel: "Later" }); if (confirm) { /* ... */ }
# CLI (Exit code is 0 for Yes, 1 for No/Cancel) ./electrobun_easydialogs --question --title="System Update" --text="Apply updates?" --ok-label="Update" --cancel-label="Later" if [ $? -eq 0 ]; then echo "Updating..." fi
Captures a single line of freeform text input from the user.
- TypeScript Signature:
async easydialogs.entry(message: string, options?: EntryOptions): Promise<string | null>
- EntryOptions:
entryText?: string— Initial text value populated in the input fieldhideText?: boolean— Masks the input text with bullet points (password mode)
- Returns:
Promise<string | null>(Returns the text string on OK, ornullif cancelled/dismissed) - Examples:
// TypeScript const username = await easydialogs.entry("Choose a handle:", { title: "Handle Registration", entryText: "anonymous" });
# CLI (Outputs value to stdout; exit code 1 if cancelled) HANDLE=$(./electrobun_easydialogs --entry --title="Handle Registration" --text="Choose a handle:" --value="anonymous")
Captures sensitive passwords. Can also capture username credentials simultaneously.
- TypeScript Signature:
async easydialogs.password(options?: PasswordOptions): Promise<[string, string] | string | null>
- PasswordOptions:
username?: boolean— If set totrue, renders separate fields for username and password
- Returns:
- If
usernameistrue:Promise<[string, string] | null>as[username, password] - If
usernameisfalse:Promise<string | null>aspassword
- If
- Examples:
// TypeScript (Password Only) const masterKey = await easydialogs.password({ title: "Decryption Key Required" }); // TypeScript (Credentials Mode) const creds = await easydialogs.password({ title: "Server Log In", username: true }); if (creds) { const [user, pass] = creds; }
# CLI (Outputs to stdout; Exit code 1 if cancelled) # Password Only: PASSWORD=$(./electrobun_easydialogs --password --title="Decryption Key Required") # Credentials Mode (outputs delimited by separator, e.g., "username|password"): CREDS=$(./electrobun_easydialogs --password --title="Server Log In" --username --separator="|")
Selects a numeric value using a slider scale.
- TypeScript Signature:
async easydialogs.scale(message: string, options?: ScaleOptions): Promise<number | null>
- ScaleOptions:
value?: number— Default starting value (default:0)minValue?: number— Minimum value limit (default:0)maxValue?: number— Maximum value limit (default:100)step?: number— Numeric interval/increment stephideValue?: boolean— Hides the live number display bubble next to the sliderprintPartial?: boolean— Prints/outputs partial decimal/float adjustments rather than rounded integers
- Returns:
Promise<number | null> - Examples:
// TypeScript const volume = await easydialogs.scale("Set volume level:", { value: 75, minValue: 0, maxValue: 100 });
# CLI VOLUME=$(./electrobun_easydialogs --scale --text="Set volume level:" --value=75 --min-value=0 --max-value=100)
Selects a date and time from an interactive calendar interface.
- TypeScript Signature:
async easydialogs.calendar(message: string, options?: CalendarOptions): Promise<string | null>
- CalendarOptions:
day?: number,month?: number,year?: number— Pre-select datedateFormat?: string— Formatting pattern
- Returns:
Promise<string | null>(ISO-8601 formatted date/time string or custom format) - Examples:
// TypeScript const dateStr = await easydialogs.calendar("Select date of birth:", { title: "Onboarding" });
# CLI DOB=$(./electrobun_easydialogs --calendar --text="Select date of birth:")
Displays fully featured custom rich-text HTML components with automatic DOM sanitization to block script injection (XSS).
- TypeScript Signature:
async easydialogs.html(htmlContent: string, options?: HTMLOptions): Promise<boolean>
- Returns:
Promise<boolean>(truefor OK/Submit,falsefor Cancel/dismiss) - Examples:
// TypeScript const isAgreed = await easydialogs.html(` <h2>Accept System Policy</h2> <p>By proceeding, you agree to follow the <strong>Acceptable Use Guidelines</strong>.</p> `, { title: "Security Notice", okLabel: "I Agree", cancelLabel: "Cancel" });
# CLI (Direct inline markup text) ./electrobun_easydialogs --html --html-text="<h3>Warning</h3><p>Production mode</p>" --ok-label="Understand" # CLI (Load content from a local file) ./electrobun_easydialogs --html --html-file="eula.html" --title="Agreement"
Constructs rich forms with mixed input fields (dropdowns, toggles, ratings, text inputs, etc.) in a single dialog.
- TypeScript Signature:
async easydialogs.forms(fields: FormField[], options?: FormsOptions): Promise<FormsResult>
- FormsOptions:
text?: string— Form subtitle/body textseparator?: string— Delimiter for output values mapping (default:'|')formsDateFormat?: string— Date/time format specifically for forms date inputsshowHeader?: boolean— Toggles visibility of the form text/subtitle header
- FormsResult Output:
interface FormsResult { button: 'ok' | 'cancel' | 'extra'; values: string[] | null; // Array of collected input strings in the declared field order }
- Examples:
// TypeScript const result = await easydialogs.forms([ { type: 'entry', label: 'App ID', value: 'com.app.service', required: true }, { type: 'combo', label: 'Environment', values: ['Dev', 'Staging', 'Prod'], value: 'Dev' }, { type: 'toggle', label: 'Enable SSL', value: true }, { type: 'rating', label: 'Rating', value: 3 } ], { title: "Application Settings", text: "Set up the application configuration metadata.", extraButton: "Restore Defaults" }); if (result.button === 'ok' && result.values) { const [appId, env, ssl, rating] = result.values; }
# CLI (Form values are printed to stdout, pipe-separated) # Exit code: 0 on Submit (OK), 2 on Extra button press ("Restore Defaults"), 1 on cancel/dismiss CONFIG=$(./electrobun_easydialogs --forms --title="App Config" \ --add-entry="App ID" \ --add-combo="Environment" --combo-values="Dev,Staging,Prod")
Inside the fields array passed to the forms() method, you can define different field elements:
Field type |
Custom TypeScript Field Schema Definition | Rendered UI Element |
|---|---|---|
entry |
{ type: 'entry'; label: string; value?: string; required?: boolean } |
Standard single-line text input |
password |
{ type: 'password'; label: string; value?: string; required?: boolean } |
Masked text input |
multiline |
{ type: 'multiline'; label: string; value?: string; required?: boolean } |
Multi-line textarea |
calendar |
{ type: 'calendar'; label: string; value?: string; required?: boolean } |
Inline date-time picker |
list |
{ type: 'list'; label: string; values?: string[]; required?: boolean } |
Scrollable value list selection |
combo |
{ type: 'combo'; label: string; values?: string[]; value?: string; required?: boolean } |
Dropdown/combobox menu |
rating |
{ type: 'rating'; label: string; value?: number | string; required?: boolean } |
Sequoia 3D 5-star rating selector |
slider |
{ type: 'slider'; label: string; value?: number | string; required?: boolean } |
Dynamic range slider bar |
toggle |
{ type: 'toggle'; label: string; value?: boolean | string; required?: boolean } |
iOS-style toggle switch |
| HTML5 Native | { type: 'text' | 'email' | 'number' | 'tel' | 'url' | 'color' | 'file' | 'checkbox' | 'radio' ...; label: string; value?: any; required?: boolean } |
Native browser input widgets matching the OS theme. |
A collection of 10 macOS Sequoia-inspired, glassmorphic 3D icons are included in the icons folder for custom-branded dialogs:
| Icon File | Recommended Usage |
|---|---|
| success.png | Task completion, successful saves, operation completed |
| info.png | Standard notifications, hints, instructions |
| warning.png | Warnings, caution alerts, non-blocking errors |
| error.png | Blocked operations, failures, exception states |
| question.png | Confirmation dialogues, interactive help prompts |
| settings.png | Setup, configuration panels, preferences |
| security.png | Authentication, permission requests, privacy settings |
| database.png | Database updates, connection statuses, backups |
| network.png | Web requests, sync status, network/API configurations |
| user.png | User logins, profile setup, account registration |
To use any icon:
import { join } from 'path';
await easydialogs.info("Configuration loaded!", {
title: "Settings Utility",
icon: join(process.cwd(), "icons/settings.png")
});The library auto-detects its execution context and selects the best strategy:
| Mode | Triggered When | Behavior |
|---|---|---|
| Electrobun Worker | Running inside compiled Electrobun app | Renders native HTML5 dialogs in a macOS WebView window |
| Vanilla Bun + Helper App | Running via bun run with helper binary present |
Spawns the compiled app in the background to present the HTML5 dialog |
| Serverless AppleScript Fallback | No Electrobun binary found, or EASYDIALOGS_FORCE_OSASCRIPT=true |
Falls back to native osascript dialogs — zero TCP ports, zero binaries required |
Electrobun EasyDialogs supports light/dark mode configuration and dynamic element color overrides.
Force any dialog to render in Light Mode, Dark Mode, or automatically detect OS theme preferences:
TypeScript:
await easydialogs.info("Dark Mode Dialog", {
theme: "dark"
});CLI:
./electrobun_easydialogs --info --text="Force Light Mode" --theme="light"Override CSS custom properties dynamically (with safety sanitization) to style dialog buttons, text, and background elements.
TypeScript:
await easydialogs.info("Custom colored theme", {
colors: {
bg: "#2b0f54", // Background
surface: "#3d1b6e", // Surface containers
text: "#ff7edb", // Text color
primary: "#39ff14", // Primary buttons/selection color
border: "#ff7edb" // Border highlights
}
});CLI:
./electrobun_easydialogs --info --text="Vibrant Accent Notification" --colors='{"primary":"#af52de","bg":"#1c1c1e","text":"#f2f2f7"}'Easily display rich formatted HTML in the dialog body. The WebView performs strict recursive DOM sanitization at runtime before rendering to eliminate injection risks (XSS).
TypeScript:
const wantsToProceed = await easydialogs.html(`
<h2>System Update Available</h2>
<p>Please review details below:</p>
<ul>
<li>Version: <strong>v2.1.0</strong></li>
<li>Security updates applied</li>
</ul>
`, {
title: "Software Center",
okLabel: "Install Now",
cancelLabel: "Postpone"
});CLI:
# Direct inline markup text
./electrobun_easydialogs --html --html-text="<h3>Safe Header</h3><p>Rendered via CLI</p>"
# Render from an HTML file
./electrobun_easydialogs --html --html-file="path/to/page.html"Note
Active XSS Protection: Unallowed tags like <script>, <iframe>, <object>, <embed>, or <style>, event handlers like onerror / onload, and javascript-protocol URLs (href="javascript:...") are completely stripped and neutralized.

