Joy DOM

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:

FieldPurpose
versionThe spec revision the document targets. Must be 1.
styleShared style rules keyed by selector.
breakpointsArray of responsive overrides. May be empty.
layoutThe 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, h1h6) or a custom kebab-case name.
  • props (optional) - id, className, style, plus tag-specific props such as src on img.
  • 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

On this page