Skip to content

Shadow DOM and Web Components

Open shadow roots just work, for composed events

Section titled “Open shadow roots just work, for composed events”

CycleWire finds the bound element by walking event.composedPath(), which includes the nodes inside open shadow roots. Composed events such as click, input, keydown, focusin and pointerdown bubble out of the shadow root to the document, so a binding inside a component works with no extra code:

<x-card>
<template shadowrootmode="open">
<button cw-action="cart#add">Add</button>
</template>
</x-card>

ctx.target is captured during dispatch, because event.target is retargeted to the host, or null, by the time your handler runs.

submit, change, toggle, beforetoggle, command, load and error do not cross the shadow boundary. To handle them inside a shadow root, observe it:

import { observe } from 'cyclewire';
class CheckoutForm extends HTMLElement {
connectedCallback() {
this.release = observe(this.shadowRoot ?? this.attachShadow({ mode: 'open' }));
}
disconnectedCallback() {
this.release?.();
}
}

observe(root) also activates the triggers inside the root and watches it for added content. An event handled by the shadow root is never handled a second time by the document.

Server-rendered components with <template shadowrootmode="open"> get their shadow roots from the HTML parser. Let CycleWire observe them all at start:

start({ actions, shadow: true });

shadow: true makes every scan look for open shadow roots and observe them, including roots in content added later. It costs an extra pass over the scanned elements, so turn it on only when you use shadow DOM.

HTML fetched later and inserted with swap or morph keeps its declarative shadow roots in browsers that support setHTMLUnsafe.

Closed roots hide their nodes from composedPath() outside the component. Call observe() on the root from inside the component; the shadow root’s own listener sees everything.

Scopes (cw-state) do not cross shadow boundaries. Named stores ($cart.count) do: every observed shadow root is searched when a store comes alive, and when content is added to it later.