+ `;
+ for (const el of document.querySelectorAll(".pat-bind")) {
+ const instance = new Pattern(el);
+ await events.await_pattern_init(instance);
+ }
+ await tick();
+
+ const spans = document.querySelectorAll("span");
+ // Each scope keeps its own "v" channel.
+ expect(spans[0].textContent).toBe("one");
+ expect(spans[1].textContent).toBe("two");
+ });
+ });
+
+ describe("5 - direction", function () {
+ it("5.1 - a target does not write back to the channel", async function () {
+ document.body.innerHTML = `
+
+
+ `;
+ const [source, target] = document.querySelectorAll("input");
+
+ for (const el of document.querySelectorAll(".pat-bind")) {
+ const instance = new Pattern(el);
+ await events.await_pattern_init(instance);
+ }
+ await tick();
+
+ expect(target.value).toBe("from-source");
+
+ // Changing the target must NOT propagate back to the source.
+ target.value = "edited";
+ target.dispatchEvent(events.input_event());
+ await tick();
+
+ expect(source.value).toBe("from-source");
+ });
+ });
+});
diff --git a/src/pat/bind/documentation.md b/src/pat/bind/documentation.md
new file mode 100644
index 000000000..079f96d41
--- /dev/null
+++ b/src/pat/bind/documentation.md
@@ -0,0 +1,103 @@
+## Description
+
+The _bind_ pattern provides declarative data binding between DOM elements.
+Select a value in one place and have a label, attribute or another field update
+elsewhere - without writing any JavaScript.
+
+## Documentation
+
+Binding works through named _channels_. Every `pat-bind` element ties one of its
+_accessors_ (a form value, an attribute, its text or its HTML) to a channel.
+Elements sharing a channel stay in sync.
+
+A minimal example - a select drives a label:
+
+
+
+
+
+When the select changes, the span's text follows.
+
+### Configuration options
+
+Each binding is configured through `data-pat-bind` with the following keys:
+
+- `key` (**required**): the channel name. Elements with the same `key` (within
+ the same scope, see below) are bound together.
+- `value`: the _accessor_ - which part of this element is bound. One of:
+ - `[]`: an attribute value, e.g. `[value]`, `[href]`,
+ `[class]`, `[aria-pressed]`. For form controls, `[value]` and
+ `[checked]` bind to the live form state (and react to `input` /
+ `change`), not to the static HTML attribute.
+ - `::text`: the element's text content (`textContent`).
+ - `::html`: the element's HTML content (`innerHTML`). Values are sanitized
+ with DOMPurify on write.
+ - _omitted_: inferred from the element. Form controls bind to their value
+ (or `checked` for checkboxes and radios); everything else binds to
+ `::text`.
+- `direction`: the data-flow direction. One of:
+ - `source`: the element writes into the channel (it is an input).
+ - `target`: the channel writes into the element (it is a display).
+ - `both`: two-way binding.
+ - _omitted_: inferred. Form controls bound to their value/checked default
+ to `both`; everything else defaults to `target`.
+
+### Multiple bindings on one element
+
+Separate several bindings with `&&`, just like _pat-inject_:
+
+
+
+This element shows the `user_name` channel as its text and mirrors the `theme`
+channel onto its `class` attribute.
+
+### Two-way binding
+
+Form controls are two-way by default, so two fields on the same channel mirror
+each other:
+
+
+
+
+Typing in either input updates the other.
+
+To bind a non-form element two-way - for example a `contenteditable` region -
+request it explicitly:
+
+
+
+### Scope
+
+By default all channels live in a single, document-wide namespace. To reuse the
+same channel name in independent regions - the typical case being repeated
+content such as a list of cards - mark a container with `data-pat-bind-scope`.
+Channel lookups resolve to the nearest scope ancestor, falling back to the
+document.
+
+
+
+
+
+
+
+### Notes
+
+- Channel values are strings, except `[checked]` which is a boolean.
+- Targets are only written once a channel has a value, so server-rendered
+ content is left untouched until a source provides data.
+- Two-way binding on `::text` / `::html` relies on a `MutationObserver` and is
+ intended for editable elements; for plain displays prefer a `target`
+ binding.
+
+### Relation to other patterns
+
+_pat-bind_ is built on the reactive primitives in `core/signals` (a small
+`signal` / `computed` / `effect` implementation shaped after the TC39 signals
+proposal). The signals are an implementation detail - the page author only ever
+writes markup.
diff --git a/src/pat/bind/index.html b/src/pat/bind/index.html
new file mode 100644
index 000000000..42715c61f
--- /dev/null
+++ b/src/pat/bind/index.html
@@ -0,0 +1,183 @@
+
+
+
+ pat-bind
+
+
+
+
+
+
pat-bind
+
+ Declarative data binding between DOM elements. Elements sharing a
+ channel (the key) stay in sync — no JavaScript
+ required.
+
+
+
+
A source drives a target
+
Pick a colour; the label and the swatch follow.
+
+
+
+
+
+
+
Two-way binding
+
+ Form controls on the same channel mirror each other. Type in
+ either field.
+
+
+
+
+
+
+
+
Binding an attribute
+
A checkbox drives the disabled state of a button.
+
+
+
+
+
+
+
Editable HTML (::html)
+
+ Edit the region below; its sanitized HTML is mirrored into the
+ preview. The ::html accessor sanitizes with
+ DOMPurify on write.
+
+
+
+ Try bold or italic text…
+
+
Preview:
+
+
+
+
+
+
+
Scope
+
+ Each card is its own scope (data-pat-bind-scope), so
+ the reused price channel stays local to the card.
+