diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx
index 8f4d7a5..20c9fd3 100644
--- a/sx/sx/nav-data.sx
+++ b/sx/sx/nav-data.sx
@@ -368,6 +368,8 @@
:children (list
{:label "Reference" :href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items}
{:label "Examples" :href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items})}
+ {:label "Spreads" :href "/sx/(geography.(spreads))"
+ :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, and the path to provide/context/emit!."}
{:label "Marshes" :href "/sx/(geography.(marshes))"
:summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."}
{:label "Isomorphism" :href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items})}
diff --git a/sx/sx/spreads.sx b/sx/sx/spreads.sx
new file mode 100644
index 0000000..0558f3b
--- /dev/null
+++ b/sx/sx/spreads.sx
@@ -0,0 +1,235 @@
+;; ---------------------------------------------------------------------------
+;; Spreads — child-to-parent communication across render boundaries
+;; ---------------------------------------------------------------------------
+
+(defcomp ~geography/spreads-content ()
+ (~docs/page :title "Spreads"
+
+ (p :class "text-stone-500 text-sm italic mb-8"
+ "A spread is a value that, when returned as a child of an element, "
+ "injects attributes onto its parent instead of rendering as content. "
+ "This inverts the normal direction of data flow: children tell parents how to look.")
+
+ ;; =====================================================================
+ ;; I. The primitives
+ ;; =====================================================================
+
+ (~docs/section :title "Three primitives" :id "primitives"
+ (p "The spread system has three orthogonal primitives. Each operates at a "
+ "different level of the render pipeline.")
+
+ (~docs/subsection :title "1. make-spread / spread? / spread-attrs"
+ (p "A spread is a value type. " (code "make-spread") " creates one from a dict of "
+ "attributes. When the renderer encounters a spread as a child of an element, "
+ "it merges the attrs onto the parent element instead of appending a DOM node.")
+ (~docs/code :code "(defcomp ~highlight (&key colour)
+ (make-spread {\"class\" (str \"highlight-\" colour)
+ \"data-highlight\" colour}))")
+ (p "Use it as a child of any element:")
+ (~docs/code :code "(div (~highlight :colour \"yellow\")
+ \"This div gets class=highlight-yellow\")")
+ (p (code "class") " values are appended (space-joined). "
+ (code "style") " values are appended (semicolon-joined). "
+ "All other attributes overwrite."))
+
+ (~docs/subsection :title "2. collect! / collected / clear-collected!"
+ (p "Render-time accumulators. Values are collected into named buckets "
+ "during rendering and retrieved at flush points. Deduplication is automatic.")
+ (~docs/code :code ";; Deep inside a component tree:
+(collect! \"cssx\" \".sx-bg-red-500{background-color:hsl(0,72%,53%)}\")
+
+;; At the flush point (once, in the layout):
+(let ((rules (collected \"cssx\")))
+ (clear-collected! \"cssx\")
+ (raw! (str \"\")))")
+ (p "This is upward communication through the render tree: "
+ "a deeply nested component contributes a CSS rule, and the layout "
+ "emits all accumulated rules as a single " (code "