Joy DOM

Extras

Beyond the basics — viewport & breakpoints, diagnostics, inline overrides, and treating JoyDom as a SwiftUI view.

JoyDom(spec:components:) renders a document on its own — none of the following is required to get pixels on screen. These are the optional surfaces you reach for as you go deeper. Each is a chainable modifier on the view (later calls win; .onEvent is last-wins).

Viewport and breakpoints

A document's breakpoints are pure data — conditions like width, orientation, and print mode. The renderer resolves which are active by comparing them against a Viewport. Without one, breakpoints stay inactive and only document-level styles apply — so the viewport is optional, needed only when a document uses @media/breakpoint overrides.

JoyDom(spec: spec, components: registry)
    .viewport(Viewport(width: 1024, orientation: .landscape))

Viewport carries the three signals joy-dom's media queries can ask about:

FieldTypeDefaultDrives
widthCGFloatwidth-based media conditions
orientationOrientation.portraitorientation: conditions
isPrintBoolfalse@media print style blocks

Only width is required — Viewport(width: 1024) is valid. A bare (width) media feature with no operator or value always matches, since a viewport always has a width.

For breakpoints that track the live layout, derive the width from a GeometryReader:

GeometryReader { proxy in
    JoyDom(spec: spec, components: registry)
        .viewport(Viewport(width: proxy.size.width))
}

You can also inject a viewport through the environment with .joyViewport(_:) higher up your view tree, instead of passing it to each JoyDom.

Diagnostics

The renderer is silent by default: unsupported properties, unknown selectors, and invalid values are skipped, not thrown. Attach .onDiagnostic to observe them — handy while authoring documents or behind #if DEBUG:

JoyDom(spec: spec, components: registry)
    .onDiagnostic { warning in
        switch warning.kind {
        case .unsupportedProperty(let name):
            print("joy-dom: unsupported property \(name)\(warning.detail)")
        case .invalidValue(let property, let value):
            print("joy-dom: invalid \(property) = \(value)")
        default:
            break
        }
    }

Each JoyWarning has a kind and a detail string. The kinds: .unsupportedProperty, .unsupportedSelector, .unsupportedAtRule, .invalidValue(property:value:), .duplicateLocalID, .duplicateSchemaID, and .other.

Inline component overrides

Beyond the registry, you can override individual nodes per instance by their schema id, with a trailing builder — handy for one-off content (a hero image, a specific button) without registering a type:

JoyDom(spec: spec, components: registry) {
    Component("hero") { Image("banner").resizable().scaledToFill() }
    if showCTA {
        Component("submit") { Button("Get started") { /* … */ } }
    }
}

Lookup for each node is locals → registry → placeholder, so a local wins over a registered factory. Overrides are render-only; events still reach the single .onEvent handler. The builder supports if/else (not loops).

JoyDom is a SwiftUI view

JoyDom is an ordinary View, so standard modifiers compose around it and you can embed it anywhere:

JoyDom(spec: spec, components: registry)
    .padding()
    .frame(maxWidth: 600)
    .background(.background)

Its configuration knobs are chainable methods, each returning a new JoyDom:

  • .onEvent { event in … } — the single event handler (Events).
  • .viewport(_:) — breakpoint resolution (above).
  • .onDiagnostic { warning in … } — renderer warnings (above).
  • .defaultFontFamily(_:) — the CSS-inherited body font (nil = system font).

Where next

On this page