Document model
The four top-level fields of every Joy DOM document.
This tutorial walks through building a small business card document from scratch and growing it across the next four chapters. We will publish the finished version in chapter 5.
Every Joy DOM document is a JSON object with exactly four top-level fields:
| Field | Purpose |
|---|---|
version | The spec revision the document targets. Must be 1. |
style | Shared style rules keyed by selector. |
breakpoints | Array of responsive overrides. May be empty. |
layout | The root node - the tree the renderer walks and displays. |
A minimal document
Start with the smallest valid Joy DOM document:
{
"version": 1,
"style": {
"div": {
"display": "flex"
}
},
"breakpoints": [],
"layout": {
"type": "div",
"children": []
}
}This document renders an empty container. Nothing visible, but every required field is present and every node declares display.
The `display` rule
Joy DOM throws if a node renders without display: "flex" or display: "none". Set it on the
node directly via props.style, or define it once on the selector - both work. See
§4.4.
Add content
The layout field is a single node. Nodes have:
type- an HTML-like tag (div,span,p,img,h1…h6) or a custom kebab-case name.props(optional) -id,className,style, plus tag-specific props such assrconimg.children(optional) - an array of nodes and primitive values (strings, numbers,null).
Add a heading and paragraph to the business card:
{
"version": 1,
"style": {
".card": {
"display": "flex",
"flexDirection": "column",
"gap": {
"value": 8,
"unit": "px"
},
"padding": {
"value": 24,
"unit": "px"
}
},
"h1": {
"display": "flex",
"fontSize": {
"value": 28,
"unit": "px"
}
},
"p": {
"display": "flex",
"fontSize": {
"value": 14,
"unit": "px"
},
"color": "#475569"
}
},
"breakpoints": [],
"layout": {
"type": "div",
"props": {
"className": ["card"]
},
"children": [
{
"type": "h1",
"children": ["Avery Chen"]
},
{
"type": "p",
"children": ["Product designer · Tokyo"]
}
]
}
}The .card class supplies the layout primitives; the type selectors (h1, p) handle the inherited typography defaults.
Validate as you go
The @joy-dom/spec package exports a Zod schema you can run on any document:
const = .();
if (!.) {
.(.);
}Use it in CI, in your build, or in a one-off script. Validation catches missing display, malformed lengths, invalid selectors, and unknown property values before they reach a renderer.
Wrap-up
The skeleton is in place. In the next chapter we layer on real styling - borders, colors, alignment - and look at how selectors compose.
Spec references: §2 Document · §3 Nodes