Specification
The Joy DOM specification - one numbered document covering the document shape, nodes, styles, properties, breakpoints, and custom components.
The Joy DOM specification defines the JSON document format and the behavior every conforming renderer must implement. Sections are numbered so you can deep-link from a PR, ticket, or chat - for example, /docs/spec#521-flexdirection points at one property.
This document is friendly and example-driven. Every code sample is a complete, valid Joy DOM fragment.
1. Overview
1.1 What Joy DOM is
Joy DOM is a JSON document format. A document describes a tree of HTML-like nodes with CSS-like styles and media-query-like breakpoints. A renderer reads the JSON and maps it to a platform UI - React on the web, SwiftUI on Apple platforms, Compose on Android, with React Native planned.
Joy DOM targets cross-platform native rendering for a focused subset of HTML and CSS. Anything documented in this specification is supported by conforming renderers; anything not documented is not.
1.2 Goals and non-goals
Goals:
- One document model rendered natively across platforms.
- Strict alignment with HTML and CSS web standards for visual rendering and interactions.
- Stable, machine-readable structure suitable for tooling and LLMs.
Non-goals:
- Full HTML and CSS coverage. Joy DOM is intentionally a subset.
- Custom syntax or features that deviate from web standards.
1.3 Document shape at a glance
A Joy DOM document has four top-level fields:
| Field | Type | Purpose |
|---|---|---|
version | integer | The spec version. Must be 1. |
style | object | Shared styles keyed by selector. |
breakpoints | array | Responsive overrides with media-query-like conditions. |
layout | node | The root node of the document tree. |
Minimal example:
{
"version": 1,
"style": {
".stack": {
"display": "flex",
"flexDirection": "column",
"gap": {
"value": 12,
"unit": "px"
}
},
"h1": {
"display": "flex",
"fontSize": {
"value": 32,
"unit": "px"
}
},
"p": {
"display": "flex"
}
},
"breakpoints": [],
"layout": {
"type": "div",
"props": {
"className": ["stack"]
},
"children": [
{
"type": "h1",
"children": ["Hello Joy DOM"]
},
{
"type": "p",
"children": ["Render structured web content across platforms."]
}
]
}
}2. Document
2.1 version
Integer identifying the spec revision the document targets. The current version is 1. Renderers reject documents whose version they do not support. See §9 Versioning.
2.2 style
Object keyed by selector. See §4 Styles for selector syntax and resolution.
2.3 breakpoints
Array of breakpoint objects. See §6 Breakpoints. May be empty.
2.4 layout
The root node of the document tree. A node has a type (an HTML tag name or custom node name), optional props, and optional children (an array of nodes or primitive values - string, number, or null). See §3 Nodes.
3. Nodes
Joy DOM supports a focused subset of HTML node types. Each subsection below documents one node. Any node's props can also bind events to actions; see §8 Events and actions.
3.1 div
Block container. Use for layout grouping.
{
"type": "div",
"props": {
"className": ["card"]
},
"children": [
{
"type": "p",
"children": ["Content"]
}
]
}3.2 span
Inline container. Use for inline grouping inside text.
3.3 p
Paragraph. Equivalent to HTML <p>.
3.4 img
Image. Required prop: src. Optional: alt. See §5.8 Media for sizing and fit properties.
{
"type": "img",
"props": {
"src": "https://example.com/photo.png",
"alt": "Photo description",
"style": {
"display": "flex",
"width": {
"value": 240,
"unit": "px"
},
"height": {
"value": 120,
"unit": "px"
},
"objectFit": "cover"
}
}
}3.5 h1 through h6
Headings. Same semantics as HTML <h1>…<h6>. Use fontSize and other typography properties from §5.6 to style them.
3.6 Custom nodes
Custom node names follow the WHATWG valid custom element name rules:
- Must contain a hyphen (
-). - Must be all-lowercase.
- Must not be a reserved name:
annotation-xml,color-profile,font-face,font-face-src,font-face-uri,font-face-format,font-face-name,missing-glyph.
Each renderer registers a handler for custom node types. See §7 Custom components.
4. Styles
Styles let you share visual rules across a document and override them per node. Joy DOM supports type selectors, class selectors, id selectors, and inline node styles.
Use shared styles for reusable document structure. Use inline styles when one node needs a value that should not be reused.
4.1 Selectors
Shared styles live in the top-level style object, keyed by selector.
| Selector | Example | Matches |
|---|---|---|
| Type | p | Nodes with type: "p". |
| Class | .title | Nodes whose props.className includes title. |
| Id | #hero | Nodes with props.id: "hero". |
4.2 Resolution order
Joy DOM resolves styles in this order, later sources overriding earlier ones:
- Type selector.
- Class selectors, in the order listed in
props.className. - Id selector.
- Breakpoint styles when a breakpoint matches.
- Inline
props.style.
Later values replace earlier values for the same property.
4.3 Inline styles
Inline styles are written on the node via props.style and take precedence over every shared selector.
{
"type": "p",
"props": {
"className": ["muted"],
"style": {
"display": "flex",
"color": "#111827"
}
},
"children": ["Inline color wins over the shared class."]
}4.4 Special rules
Conforming renderers enforce the following invariants:
- Every node must explicitly declare
display: "flex"ordisplay: "none". Renderers throw on missingdisplay. - The default
box-sizingisborder-box, notcontent-box. - Custom node names must be valid per §3.6.
- Color values use hex strings.
rgb()andrgba()are not supported.
5. Properties
This section documents every CSS property Joy DOM supports. Each entry follows the same shape: short description, values table, JSON example, and where useful a live preview rendered by @joy-dom/react. Anchors are stable: /docs/spec#521-flexdirection points at one property.
5.1 Layout and positioning
5.1.1 position
Positioning mode of the node.
| Field | Value |
|---|---|
| Type | 'absolute' | 'relative' |
| Default | 'relative' |
| Inherited | No |
absolute
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.1.2 display
Layout mode. Required on every node - see §4.4.
| Field | Value |
|---|---|
| Type | 'flex' | 'none' |
| Default | Required. |
| Inherited | No |
Set display: "flex" to render the node. Set display: "none" to hide it. Hiding inside a breakpoint override is the idiomatic way to toggle visibility responsively.
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.1.3 boxSizing
Box model behavior. The default is border-box, not content-box.
| Field | Value |
|---|---|
| Type | 'border-box' |
| Default | 'border-box' |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.1.4 zIndex
Stacking order for positioned nodes.
| Field | Value |
|---|---|
| Type | number |
| Default | 0 |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.1.5 overflow
Overflow handling.
| Field | Value |
|---|---|
| Type | 'visible' | 'hidden' | 'clip' | 'scroll' | 'auto' |
| Default | 'visible' |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.1.6 top, left, bottom, right
Offset of an absolutely-positioned node from each side of its containing block.
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | auto |
| Inherited | No |
Used with position: "absolute". See §5.1.1 for a worked example.
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2 Flexbox
Every Joy DOM container is a flex container (via display: "flex"). The flex properties below describe its layout axis, alignment, growth, and spacing.
5.2.1 flexDirection
Main axis direction of a flex container.
| Field | Value |
|---|---|
| Type | 'row' | 'column' |
| Default | 'row' |
| Inherited | No |
1
2
3
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.2 flexGrow
Growth factor along the main axis.
| Field | Value |
|---|---|
| Type | number |
| Default | 0 |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.3 flexShrink
Shrink factor along the main axis.
| Field | Value |
|---|---|
| Type | number |
| Default | 1 |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.4 flexBasis
Initial size of an item before growth or shrink is applied.
| Field | Value |
|---|---|
| Type | Length<'px' | '%'> | 'auto' |
| Default | 'auto' |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.5 flexWrap
Whether items wrap onto multiple lines.
| Field | Value |
|---|---|
| Type | 'nowrap' | 'wrap' |
| Default | 'nowrap' |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.6 justifyContent
Alignment of items along the main axis.
| Field | Value |
|---|---|
| Type | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' |
| Default | 'flex-start' |
| Inherited | No |
A
B
C
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.7 alignItems
Alignment of items along the cross axis.
| Field | Value |
|---|---|
| Type | 'flex-start' | 'flex-end' | 'center' | 'stretch' |
| Default | 'stretch' |
| Inherited | No |
centered
across
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.8 alignSelf
Per-item cross-axis alignment override.
| Field | Value |
|---|---|
| Type | 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' |
| Default | 'auto' |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.9 gap
Spacing between flex items along both axes.
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.10 rowGap
Spacing between flex items along the cross axis.
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.11 columnGap
Spacing between flex items along the main axis.
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.2.12 order
Item ordering inside a flex container. Use inside a breakpoint override to reorder responsively.
| Field | Value |
|---|---|
| Type | number |
| Default | 0 |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.3 Sizing
5.3.1 width
| Field | Value |
|---|---|
| Type | Length<'px' | '%'> |
| Default | auto |
| Inherited | No |
5.3.2 height
| Field | Value |
|---|---|
| Type | Length<'px' | '%'> |
| Default | auto |
| Inherited | No |
5.3.3 minWidth
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
5.3.4 maxWidth
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | none |
| Inherited | No |
5.3.5 minHeight
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
5.3.6 maxHeight
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | none |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.4 Spacing
5.4.1 padding
Inner spacing. Accepts a single length applied to all sides, or per-side lengths.
| Field | Value |
|---|---|
| Type | Length<'px'> | { top, right, bottom, left } (each Length<'px'>) |
| Default | 0px |
| Inherited | No |
{
"padding": {
"top": {
"value": 12,
"unit": "px"
},
"right": {
"value": 16,
"unit": "px"
},
"bottom": {
"value": 12,
"unit": "px"
},
"left": {
"value": 16,
"unit": "px"
}
}
}5.4.2 margin
Outer spacing. Same shape as padding.
| Field | Value |
|---|---|
| Type | Length<'px'> | { top, right, bottom, left } (each Length<'px'>) |
| Default | 0px |
| Inherited | No |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.5 Borders
5.5.1 borderWidth
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | No |
5.5.2 borderColor
Hex string. rgb() / rgba() are not supported.
| Field | Value |
|---|---|
| Type | string |
| Default | 'transparent' |
| Inherited | No |
5.5.3 borderStyle
| Field | Value |
|---|---|
| Type | 'solid' | 'none' |
| Default | 'none' |
| Inherited | No |
5.5.4 borderRadius
Corner rounding. Accepts a single length applied to all corners, or per-corner lengths.
| Field | Value |
|---|---|
| Type | Length<'px'> | { topLeft, topRight, bottomRight, bottomLeft } (each Length<'px'>) |
| Default | 0px |
| Inherited | No |
rounded chip
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.6 Typography
Typography properties apply to text nodes: p, h1–h6, and span. Most inherit from ancestor nodes.
5.6.1 fontFamily
| Field | Value |
|---|---|
| Type | string |
| Default | system default |
| Inherited | Yes |
5.6.2 fontSize
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 16px |
| Inherited | Yes |
5.6.3 fontWeight
| Field | Value |
|---|---|
| Type | 'normal' | 'bold' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 |
| Default | 'normal' (400) |
| Inherited | Yes |
5.6.4 fontStyle
| Field | Value |
|---|---|
| Type | 'normal' | 'italic' |
| Default | 'normal' |
| Inherited | Yes |
5.6.5 color
Hex string. rgb() / rgba() are not supported.
| Field | Value |
|---|---|
| Type | string |
| Default | '#000000' |
| Inherited | Yes |
5.6.6 textDecoration
| Field | Value |
|---|---|
| Type | 'none' | 'underline' | 'line-through' |
| Default | 'none' |
| Inherited | No |
5.6.7 textAlign
| Field | Value |
|---|---|
| Type | 'left' | 'center' | 'right' |
| Default | 'left' |
| Inherited | Yes |
5.6.8 textTransform
| Field | Value |
|---|---|
| Type | 'none' | 'uppercase' | 'lowercase' |
| Default | 'none' |
| Inherited | Yes |
5.6.9 lineHeight
Multiplier of the font size.
| Field | Value |
|---|---|
| Type | number |
| Default | 1.2 |
| Inherited | Yes |
5.6.10 letterSpacing
| Field | Value |
|---|---|
| Type | Length<'px'> |
| Default | 0px |
| Inherited | Yes |
5.6.11 textOverflow
Behavior when text overflows its container.
| Field | Value |
|---|---|
| Type | 'clip' | 'ellipsis' |
| Default | 'clip' |
| Inherited | No |
text-overflow: "ellipsis" requires all of:
overflow: "hidden"on the same node.whiteSpace: "nowrap"on the same node.- A constrained width (from the parent).
- Text rendered directly inside that node (for example, inside a
p).
5.6.12 whiteSpace
| Field | Value |
|---|---|
| Type | 'normal' | 'nowrap' |
| Default | 'normal' |
| Inherited | Yes |
Typography sample
A few inherited typography properties applied to a paragraph.
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.7 Backgrounds
5.7.1 backgroundColor
Hex string. rgb() / rgba() are not supported.
| Field | Value |
|---|---|
| Type | string |
| Default | 'transparent' |
| Inherited | No |
5.7.2 opacity
| Field | Value |
|---|---|
| Type | number (0–1) |
| Default | 1 |
| Inherited | No |
5.7.3 Background images
The background-image CSS property is not yet supported. Use an img node as a positioned background layer instead:
- Set
position: "relative"on the wrapper. - Add an
imgchild positioned withposition: "absolute"and pinned to the wrapper's edges. - Give the foreground content a higher
zIndexthan the image.
{
"type": "div",
"props": {
"style": {
"display": "flex",
"position": "relative",
"overflow": "hidden",
"height": {
"value": 320,
"unit": "px"
}
}
},
"children": [
{
"type": "img",
"props": {
"src": "https://example.com/photo.png",
"style": {
"display": "flex",
"position": "absolute",
"top": {
"value": 0,
"unit": "px"
},
"left": {
"value": 0,
"unit": "px"
},
"right": {
"value": 0,
"unit": "px"
},
"bottom": {
"value": 0,
"unit": "px"
},
"width": {
"value": 100,
"unit": "%"
},
"height": {
"value": 100,
"unit": "%"
},
"objectFit": "cover",
"zIndex": 0
}
}
},
{
"type": "div",
"props": {
"style": {
"display": "flex",
"position": "relative",
"zIndex": 1
}
},
"children": [
{
"type": "p",
"children": ["Foreground content"]
}
]
}
]
}Use breakpoint overrides to change objectFit, objectPosition, wrapper size, or foreground spacing per width or orientation.
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
5.8 Media
img nodes accept the properties below to control how the source asset scales and positions within the node's box.
5.8.1 objectFit
| Field | Value |
|---|---|
| Type | 'fill' | 'contain' | 'cover' | 'none' |
| Default | 'fill' |
| Inherited | No |
5.8.2 objectPosition
Anchor point inside the node's box.
| Field | Value |
|---|---|
| Type | { horizontal: 'left' | 'center' | 'right', vertical: 'top' | 'center' | 'bottom' } |
| Default | { horizontal: 'center', vertical: 'center' } |
| Inherited | Yes |
Cross-renderer parity: React ✅ · Swift ⚠️ pre-release · Kotlin ⚠️ pre-release
6. Breakpoints
Breakpoints apply node and style overrides when media-query-like conditions match. Use them for layout changes, visibility, order, print output, and device-specific typography.
Joy DOM keeps one primary node tree. Breakpoints change styles and node props inside that tree - they do not introduce a parallel tree.
6.1 Breakpoint object
A breakpoint has three required fields:
| Field | Description |
|---|---|
conditions | Array of conditions that activate the breakpoint. |
nodes | Node prop overrides keyed by node id. |
style | Shared style overrides keyed by selector. |
6.2 Conditions
Joy DOM supports four condition types and three logical operators.
| Condition | Values |
|---|---|
| Media type | print |
| Width feature | width with >, <, >=, or <= in px |
| Orientation feature | portrait or landscape |
| Logical operators | and, or, not |
Width:
{
"type": "feature",
"name": "width",
"operator": "<",
"value": 768,
"unit": "px"
}CSS equivalent: @media (width < 768px) { ... }
Print:
{
"type": "type",
"value": "print"
}CSS equivalent: @media print { ... }
Width and orientation, joined by and:
{
"op": "and",
"conditions": [
{
"type": "feature",
"name": "width",
"operator": ">=",
"value": 768,
"unit": "px"
},
{
"type": "feature",
"name": "orientation",
"value": "landscape"
}
]
}CSS equivalent: @media (width >= 768px) and (orientation: landscape) { ... }
Negation with not:
{
"op": "not",
"condition": {
"type": "type",
"value": "print"
}
}CSS equivalent: @media not print { ... }
6.3 Matching algorithm
When more than one breakpoint matches, Joy DOM chooses exactly one:
- The breakpoint with the more specific condition set wins.
- If specificity is tied, the later breakpoint in the array wins.
Multiple matching breakpoints are not merged.
6.4 Common patterns
Change order. Use order inside a breakpoint to reorder siblings:
{
"conditions": [
{
"type": "feature",
"name": "width",
"operator": "<",
"value": 768,
"unit": "px"
}
],
"nodes": {
"sidebar": {
"style": {
"order": 2
}
}
},
"style": {}
}Remove order from the override to fall back to the primary tree order.
Toggle visibility. Use display: "none" on the primary node or inside a breakpoint override:
{
"conditions": [
{
"type": "feature",
"name": "width",
"operator": "<",
"value": 768,
"unit": "px"
}
],
"nodes": {
"desktop-nav": {
"style": {
"display": "none"
}
}
},
"style": {}
}Override props. Breakpoint node props merge with primary node props - only the listed fields change:
{
"conditions": [
{
"type": "feature",
"name": "width",
"operator": "<",
"value": 768,
"unit": "px"
}
],
"nodes": {
"hero-title": {
"style": {
"fontSize": {
"value": 40,
"unit": "px"
}
}
}
},
"style": {}
}7. Custom components
A custom node is a node with a type that follows the rules in §3.6 Custom nodes. Each renderer registers a handler keyed by the custom name; when the renderer encounters a node of that type, it delegates rendering to the registered handler.
{
"type": "my-button",
"props": {
"id": "primary-action"
},
"children": ["Continue"]
}The document owns the node type and props. The renderer owns the component implementation.
7.1 Component props
Renderers pass these props to a custom component:
| Prop | Description |
|---|---|
children | Rendered child nodes and primitive values. |
node | The original Joy DOM node. |
id | The resolved node id. |
className | Renderer-generated classes plus document classes. |
style | Resolved inline style for the platform renderer. |
If a document uses a custom node type without a registered component, the renderer throws an error.
7.2 Registration
Registration is renderer-specific. See:
8. Events and actions
A node's props may bind a DOM event to a named action. A binding is an object with type: "action", the action name, and optional params:
{
"type": "div",
"props": {
"className": ["buy"],
"onclick": {
"type": "action",
"action": "checkout",
"params": {
"sku": "joy-01"
}
}
},
"children": ["Buy now"]
}The document names an action; the host supplies the handler. Nothing executable lives in the JSON, so a document stays serializable and safe to ship over the wire.
8.1 Event names
Four events bind, each matching its HTML counterpart:
| Property | Fires on |
|---|---|
onclick | A click or tap on the node. |
onfocus | The node gaining focus. |
onblur | The node losing focus. |
onchange | A committed value change on an input node. |
8.2 Action binding
| Field | Type | Required | Description |
|---|---|---|---|
type | "action" | Yes | Marks the value as an action binding. |
action | string | Yes | The action name the host resolves to a handler. |
params | object | No | Static arguments passed to the handler with the event. |
A renderer resolves each binding against a host-provided action map when it renders. How resolution and missing actions behave is renderer-specific; see the React renderer.
9. Versioning
The version field at the top of every document declares the spec revision the document targets. The current version is 1. Renderers reject documents whose version they do not support.
Versioning is semantic at the JSON Schema level: a major bump can break document compatibility; minor and patch bumps only add or refine properties.
Changelog
- 2026-06-05. Added §8 Events and actions: a node's
propscan bindonclick,onfocus,onblur, andonchangeto named actions. - 2026-05-22. Spec restructured into a single numbered document. Previous pages (
specification,styles,css,breakpoints,text-styles,background-images,components) folded into this page.