diff --git a/doc/html-proxy.md b/doc/html-proxy.md
new file mode 100644
index 0000000..95b4b24
--- /dev/null
+++ b/doc/html-proxy.md
@@ -0,0 +1,66 @@
+# Skooma.js
+
+```js
+import {html} from "skooma.js"
+```
+
+A functional-friendly helper library for procedural DOM generation and
+templating, with support for reactive state objects.
+
+## Overview
+
+```js
+const text = new State({value: "Skooma is cool"})
+setTimeout(() => {text.value = "Skooma is awesome!"}, 1e5)
+
+document.body.append(html.div(
+ html.h1("Hello, World!"),
+ html.p(text, {class: "amazing"}),
+ html.button("Show Proof", {click: event => { alert("It's true!") }})
+))
+```
+
+## Interface / Examples
+
+### Basic DOM generation
+
+Accessing the `html` proxy with any string key returns a new node generator function:
+
+```js
+html.div("Hello, World!")
+```
+
+Attributes can be set by passing objects to the generator:
+
+```js
+html.div("Big Text", {style: "font-size: 1.4em"})
+```
+
+Complex structures can easily achieved by nesting generator functions:
+
+```js
+html.div(
+ html.p(
+ html.b("Bold Text")
+ )
+)
+```
+
+For convenience, arrays assigned as attributes will be joined with spaces:
+
+```js
+html.a({class: ["button", "important"]})
+```
+
+Assigning a function as an attribute will instead attach it as an event listener:
+
+```js
+html.button("Click me!", {click: event => {
+ alert("You clicked the button.")
+}})
+```
+
+
+
+Generators can be called with many arguments. Arrays get iterated recursively as if they were part of a flat argument list.
+
diff --git a/doc/overview.md b/doc/overview.md
new file mode 100644
index 0000000..a8b9545
--- /dev/null
+++ b/doc/overview.md
@@ -0,0 +1,93 @@
+## Skooma.js
+
+### HTML Proxy
+
+The proxy object that does the actual HTML generation.
+
+```js
+document.body.append(html.div(
+ html.span("Hello, World!")
+))
+```
+
+### Handle helper
+
+Wraps a funcion of the signature `event, ... -> value` so that
+`event.preventDefault` gets called before running the function.
+
+```js
+button.addEventListener("click",
+ handle(() => console.log("click"))
+)
+```
+
+### Fragment helper
+
+Wraps a list of elements in a new document fragment.
+
+```js
+const spans = fragment(
+ html.span("First span"),
+ html.span("Second span")
+)
+document.body.append(spans.cloneNode())
+```
+
+### Text helper
+
+When called as a normal function, returns a new text node with the given
+content. Unlike `document.createTextNode`, it does not fail for non-string
+values.
+
+```js
+const node = text("Hello, World!")
+```
+
+When used as a tagged template, returns a document fragment containing text
+nodes and interpolated values. DOM nodes can be interpolated into the document
+fragment.
+
+```js
+const description = text`Interpolate ${html.b("bold")} text`
+```
+
+For consistency, even tagged templates with no interpolated variables will
+always return a document fragment.
+
+## State.js
+
+### AbortRegistry
+
+`FinalizationRegistry` that takes an `AbortController` and aborts it whenever
+the registered value gets collected.
+
+### ChangeEvent
+
+The event class emitted when a change is detected on a skooma state.
+Provides the `final` getter.
+
+### MapStorage
+
+A utility class that simulates the `Storage` API but is backed by a map. Can be
+used as fallback in environments where persistent storages aren't available.
+
+### SimpleState
+
+Base state class that all other states inherit from, used primarily for class
+checking, as the `State` class introduces behaviours that may be undesireable
+when inheriting.
+
+### State
+
+The main state class that does all the magic.
+
+### ForwardState
+
+Proxy to a named property on another State to be used with APIs that only accept
+single-value states.
+
+### StoredState
+
+State class that is backed by a Storage instead of an internal proxy.
+
+## domLense.js
diff --git a/readme.md b/readme.md
index 8bdea5d..dc9f29f 100644
--- a/readme.md
+++ b/readme.md
@@ -1,148 +1 @@
# Skooma
-
-A functional-friendly helper library for procedural DOM generation and
-templating.
-
-```js
-import {html} from "skooma.js"
-```
-
-## Overview
-
-```js
-const text = new State({value: "Skooma is cool"})
-setTimeout(() => {text.value = "Skooma is awesome!"}, 1e5)
-
-document.body.append(html.div(
- html.h1("Hello, World!"),
- html.p(text, {class: "amazing"}),
- html.button("Show Proof", {click: event => { alert("It's true!") }})
-))
-```
-
-## Interface / Examples
-
-### Basic DOM generation
-
-Accessing the `html` proxy with any string key returns a new node generator
-function:
-
-```js
-html.div("Hello, World!")
-```
-
-Attributes can be set by passing objects to the generator:
-
-```js
-html.div("Big Text", {style: "font-size: 1.4em"})
-```
-
-Complex structures can easily achieved by nesting generator functions:
-
-```js
-html.div(
- html.p(
- html.b("Bold Text")
- )
-)
-```
-
-For convenience, arrays assigned as attributes will be joined with spaces:
-
-```js
-html.a({class: ["button", "important"]})
-```
-
-Assigning a function as an attribute will instead attach it as an event
-listener:
-
-```js
-html.button("Click me!", {click: event => {
- alert("You clicked the button.")
-}})
-```
-
-
-
-Generators can be called with many arguments. Arrays get iterated recursively as
-if they were part of a flat argument list.
-
-### Generating Text Nodes
-
-```js
-text("Hello, World")
-// Wraps document.createTextNode
-text()
-// Defaults to empty string instead of erroring
-text(null)
-// Non-string arguments still error
-
-text`Hello, World!`
-// returns a new document fragment containing the text node "Hello, World!"
-text`Hello, ${user}!`
-// returns a document fragment containing 3 nodes:
-// "Hello, ", the interpolated value of `user` and "!"
-text`Hello, ${html.b(user)}!`
-// Text node for Hello, the tag with the user's name, and a text node for !
-```
-
-## handle
-
-```js
-import {handle} from 'skooma.js'
-```
-
-Since it is common for event handlers to call `preventDefault()`, skooma
-provides a helper function called `handle` with the following definition:
-
-```js
-fn => event => { event.preventDefault(); return fn(event) }
-```
-
-## A few more examples:
-
-Create a Button that deletes itself:
-
-```js
-document.body.append(
- html.button("Delete Me", {click: event => event.target.remove()})
-)
-```
-
-Turn a two-dimensional array into an HTML table:
-```js
-const table = rows =>
- html.table(html.tbody(rows.map(
- row => html.tr(row.map(
- cell => html.rd(cell, {dataset: {
- content: cell.toLowerCase(),
- }})
- ))
- )))
-```
-
-A list that you can add items to
-```js
-let list, input = ""
-document.body.append(html.div([
- list=html.ul(),
- html.input({type: 'text', input: e => input = e.target.value}),
- html.button({click: event => list.append(html.li(input))}, "Add"),
-]))
-```
-
-A list that you can also delete items from
-```js
-const listItem = content => html.li(
- html.span(content), " ", html.a("[remove]", {
- click: event => event.target.closest("li").remove(),
- style: { cursor: 'pointer', color: 'red' },
- })
-)
-let list, input = ""
-document.body.append(html.div([
- list=html.ul(),
- html.input({type: 'text', input: e => input = e.target.value}),
- html.button({click: event => list.append(listItem(input))}, "Add"),
-]))
-```