Overview
Render Joy DOM documents with Compose Multiplatform.
The Kotlin renderer (joy-dom-kotlin) decodes a Joy DOM JSON document and renders it as Compose UI. It's a Compose Multiplatform library: the modules build for JVM (desktop) and iOS today, with Android on the roadmap. The public package root is com.j0y.joy.dom.
Status: pre-release
The renderer is split into Gradle modules under kotlin/ and isn't published to Maven yet. Use it
locally by including the modules in your build.
Render a document
JoyDomCompose is the entry point. It probes the available width, resolves the cascade, and walks the tree inline:
import androidx.compose.runtime.Composable
import com.j0y.joy.dom.render.compose.JoyDomCompose
import com.j0y.joy.dom.serialization.Spec
@Composable
fun InvoiceScreen(spec: Spec) {
JoyDomCompose(spec = spec)
}JoyDomCompose measures itself with BoxWithConstraints, derives a viewport from the constraints and density, resolves spec.style and breakpoints, then renders each node: flex containers as Compose layout, text nodes as Text, and img nodes through a painter you supply. A second overload takes a pre-resolved ResolvedSpec when you want to control the viewport yourself.
Custom components
Pass a JoyDomComponents map keyed by the kebab-case node type. Each value is a composable that receives the resolved node, with children, style, and props already resolved on it:
import androidx.compose.runtime.Composable
import com.j0y.joy.dom.render.compose.JoyDomComponent
import com.j0y.joy.dom.render.compose.JoyDomComponents
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.intOrNull
val stars: JoyDomComponent = { node ->
val value = (node.propsExtras["value"] as? JsonPrimitive)?.intOrNull ?: 0
Row { repeat(5) { i -> Text(if (i < value) "★" else "☆") } }
}
val components: JoyDomComponents = mapOf("rating-stars" to stars)
JoyDomCompose(spec = spec, components = components)Read props off node.propsExtras (each value is a JsonElement), children off node.children, and resolved style off node.style. Built-in types (div, span, p, h1–h6, img) never reach this map. For img loading, pass a painterFactory: @Composable (src: String) -> Painter?.
Events and actions
Build a handler config with the events { } DSL and pass it as events. Handlers are keyed by the binding's action name; params carries the binding's static arguments:
import com.j0y.joy.dom.render.compose.events
import com.j0y.joy.dom.render.compose.MissingAction
val actions = events {
missingAction = MissingAction.Ignore // or .Throw (the default)
action("checkout") {
val sku = params?.getStringOrNull("sku")
// ...
}
}
JoyDomCompose(spec = spec, events = actions)Loading documents
Decode with the pre-configured JoyDomJson instance:
import com.j0y.joy.dom.serialization.JoyDomJson
import com.j0y.joy.dom.serialization.Spec
val spec = JoyDomJson.decodeFromString(Spec.serializer(), jsonString)JoyDomJson matches the format @joy-dom/spec defines, so documents are portable across renderers.
Module map
| Module | Purpose |
|---|---|
:dom-serialization | Spec data classes + kotlinx.serialization (the configured JoyDomJson). |
:dom-layout | Style cascade and resolution: resolve(spec, viewport) → ResolvedSpec. |
:dom-render | Platform-agnostic Renderer<T, S> visitor + HeadlessRenderer. |
:dom-render-compose | The Compose binding (JoyDomCompose). |
:dom-dsl | Optional Kotlin DSL for building Spec objects programmatically. |
:dom-converter:html | HTML ↔ Spec conversion. |
Local development
cd kotlin
./gradlew buildSample apps live under kotlin/sample-desktop (a Compose Desktop sample browser) and kotlin/sample-render-host (a JVM Compose Desktop subprocess that backs the Swift demo).
Where next
- Specification — properties and breakpoints supported across renderers.