diff --git a/demo/propagator-toolbar.html b/demo/propagator-toolbar.html
new file mode 100644
index 0000000..33349ba
--- /dev/null
+++ b/demo/propagator-toolbar.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+ Toolbar Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/folk-toolbar.ts b/src/folk-toolbar.ts
new file mode 100644
index 0000000..2d61bfb
--- /dev/null
+++ b/src/folk-toolbar.ts
@@ -0,0 +1,130 @@
+import { FolkEventPropagator } from './folk-event-propagator.ts';
+import { css } from './common/tags.ts';
+
+const styles = new CSSStyleSheet();
+styles.replaceSync(css`
+ :host {
+ position: fixed;
+ bottom: 16px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: white;
+ padding: 8px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ gap: 8px;
+ }
+
+ button {
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ background: white;
+ cursor: pointer;
+ }
+
+ button.active {
+ background: #eee;
+ }
+`);
+
+export class FolkToolbar extends HTMLElement {
+ static tagName = 'folk-toolbar';
+
+ static define() {
+ customElements.define(this.tagName, this);
+ }
+
+ #mode: 'idle' | 'connecting' = 'idle';
+ #sourceElement: Element | null = null;
+ #connectBtn: HTMLButtonElement;
+
+ constructor() {
+ super();
+
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [styles];
+
+ const connectBtn = document.createElement('button');
+ connectBtn.textContent = 'Connect Elements';
+ connectBtn.addEventListener('click', () => this.toggleConnectionMode());
+ this.#connectBtn = connectBtn;
+ shadow.appendChild(connectBtn);
+
+ document.addEventListener('click', this.handleDocumentClick.bind(this));
+ }
+
+ toggleConnectionMode() {
+ if (this.#mode === 'idle') {
+ this.#mode = 'connecting';
+ this.#sourceElement = null;
+ document.body.style.cursor = 'crosshair';
+ this.#connectBtn.classList.add('active');
+ this.#connectBtn.textContent = 'Select Source Element...';
+ } else {
+ this.#mode = 'idle';
+ this.#sourceElement = null;
+ document.body.style.cursor = '';
+ this.#connectBtn.classList.remove('active');
+ this.#connectBtn.textContent = 'Connect Elements';
+ }
+ }
+
+ handleDocumentClick(event: MouseEvent) {
+ if (this.#mode !== 'connecting') return;
+
+ // Prevent clicking toolbar itself
+ if (event.composedPath().includes(this)) return;
+
+ event.preventDefault();
+
+ const target = event.target as Element;
+
+ if (!this.#sourceElement) {
+ // First click - select source
+ this.#sourceElement = target;
+ this.#connectBtn.textContent = 'Select Target Element...';
+ } else {
+ // Second click - create connection
+ this.createConnection(this.#sourceElement, target);
+ this.#sourceElement = null;
+ this.#mode = 'idle';
+ document.body.style.cursor = '';
+ this.#connectBtn.classList.remove('active');
+ this.#connectBtn.textContent = 'Connect Elements';
+ }
+ }
+
+ createConnection(source: Element, target: Element) {
+ const sourceId = source.id || this.ensureElementId(source);
+ const targetId = target.id || this.ensureElementId(target);
+
+ // hack because we gotta sort out usage of constructor vs connectedCallback
+ const propagator = new DOMParser().parseFromString(
+ `
+
+ `,
+ 'text/html'
+ ).body.firstElementChild;
+
+ if (!customElements.get('folk-event-propagator')) {
+ FolkEventPropagator.define();
+ }
+ if (propagator) {
+ document.body.appendChild(propagator);
+ }
+ }
+
+ ensureElementId(element: Element): string {
+ if (!element.id) {
+ element.id = `folk-element-${Math.random().toString(36).slice(2)}`;
+ }
+ return element.id;
+ }
+}