Skip to content

Events

Components communicate through two event buses: a local one per app and a global one shared across all apps. Both are built on CustomEvent and an EventTarget.

For ordinary DOM events inside a component there is on. The listener is removed automatically on unmount.

on(part("button"), "click", () => open.update((v) => !v));
on(window, "resize", onResize, { passive: true });

Signature: on(target, event, handler, options?)options is the native AddEventListenerOptions.

emit / listen on the context talk to the app-internal bus. With it, components of the same app talk to each other without knowing one another directly.

// Component A
emit("cart:add", { sku: "abc", qty: 1 });
// Component B
listen("cart:add", (detail) => {
console.log("added:", detail);
});

The same bus is also available directly on the app:

app.emit("cart:add", { sku: "abc" });
app.listen("cart:add", (detail) => { /* … */ });

The global bus is a singleton shared across all Rivet apps in the document. Reachable via global on the context or on the app.

// inside a component
global.emit("theme:change", { mode: "dark" });
global.listen("theme:change", (detail) => applyTheme(detail));
// on the app
app.global.emit("theme:change", { mode: "dark" });

Use the global bus for cross-cutting concerns (theme, auth status, locale change). For anything that belongs to one app, the local bus remains the better choice.

BusScopeCleanup in contextUse case
Localone createRivet() appautomaticcomponents of one app
Globalall apps in the documentautomaticcross-app signals

The detail is arbitrary (unknown). Cast or validate it in the handler:

type CartAdd = { sku: string; qty: number };
listen("cart:add", (detail) => {
const { sku, qty } = detail as CartAdd;
});