Skip to content

Components

Matrix components are Web Components (custom elements) that ARE actors. They register with the bus, declare accepts/emits, render to a shadow DOM, and participate in the same dispatch system as any headless actor. The browser is a runner; every tab connects to NATS and mounts its own component tree.

Pages in this section

PageWhat you'll learn
Web ComponentsWhy Matrix UI is built on standard custom elements, and the constraints that imposes.
MatrixActorHtmlElementThe browser actor base class — Pattern A (all-in-one) and Pattern B (separate actor).
UI component lifecycleconnectedCallback, disconnectedCallback, _receiveContext, ready, the $join handshake.
Shadow DOM and renderingstatic template, the render(state) reactive path, morphdom, theming.
Component packagingWhat goes in matrix.json, how a UI package gets served, vertical-slice rule.

The vertical-slice rule

Every package that has a user-facing function ships BOTH a backend actor AND a browser UI component. The shell (matrix-web) contains ZERO domain UI code.

This is non-negotiable. See WORKSTREAMS/matrix-web/VERTICAL-SLICE-RULE.md. Components in this section are how you deliver that UI half.

Quick start

ts
import { MatrixActorHtmlElement, MatrixActor } from '@open-matrix/core';

class MyWidget extends MatrixActorHtmlElement {
  static accepts = { 'widget.set-data': { description: 'Update the displayed value', value: 'string' } };
  static emits = { 'widget.value-changed': { description: 'Fired when value changes', schema: { value: { type: 'string', description: 'New value' } } } };
  static template = `
    <style>:host { display: block; padding: 1rem; } button { font: inherit; }</style>
    <div class="display"></div>
    <button data-action="cycle">Cycle</button>
  `;

  protected async _onContextUpdated() {
    this.shadowRoot!.querySelector('.display')!.textContent = String(this.state.value ?? '—');
  }

  onWidgetSetData(payload: { value: string }) {
    this.setState({ value: payload.value });
    this.emit('widget.value-changed', { value: payload.value });
  }

  onCycle() {
    this.onWidgetSetData({ value: String(Date.now()) });
  }
}

customElements.define('my-widget', MyWidget);

<my-widget></my-widget> placed inside a <matrix-dsl-host> root will:

  1. Register with its parent shell via $join.
  2. Receive a context derived from the parent's mount.
  3. Bootstrap, attach a shadow DOM with the template, wire data-action events.
  4. Be reachable from any actor on the bus via mypage.my-widget.

See also

Source: projects/matrix-3/packages/core/src/framework/MatrixActorHtmlElement.ts:216-447.