Appearance
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
| Page | What you'll learn |
|---|---|
| Web Components | Why Matrix UI is built on standard custom elements, and the constraints that imposes. |
| MatrixActorHtmlElement | The browser actor base class — Pattern A (all-in-one) and Pattern B (separate actor). |
| UI component lifecycle | connectedCallback, disconnectedCallback, _receiveContext, ready, the $join handshake. |
| Shadow DOM and rendering | static template, the render(state) reactive path, morphdom, theming. |
| Component packaging | What 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:
- Register with its parent shell via
$join. - Receive a context derived from the parent's mount.
- Bootstrap, attach a shadow DOM with the
template, wiredata-actionevents. - Be reachable from any actor on the bus via
mypage.my-widget.
See also
- Define an actor — the headless half.
- Actor lifecycle — the
$join/$join_ackhandshake. - Vertical Slice Rule.
Source:
projects/matrix-3/packages/core/src/framework/MatrixActorHtmlElement.ts:216-447.