The DOM builder API provides a clean, declarative way to construct HTML elements from Space Lua. It is typically used to build Widgets for rendering dynamic UI in your pages.

The full API reference is at dom.

Basic usage

Every HTML tag is available as a function on the dom table. Call it with a table of attributes and children:

dom.div {
  class = "my-container",
  dom.h2 { "Hello!" },
  dom.p { "This is a paragraph." }
}

This creates a <div> with a class attribute, containing an <h2> and a <p>.

Attributes and content

String keys in the table become HTML attributes. Numeric entries become children:

dom.a {
  href = "https://example.com",
  "Click here"
}

Markdown support: String children are automatically processed as markdown. So "**bold**" renders as bold text.

Nested elements: Other dom.* calls can be nested as children to build up a tree.

Event handlers

Attributes starting with on are registered as event listeners:

dom.button {
  onclick = function()
    editor.flashNotification("Clicked!")
  end,
  "Click me"
}

This is equivalent to calling addEventListener("click", fn) on the button element.

Rendering as a widget

DOM elements need to be wrapped in a widget to display on a page. Use widget.html for inline or widget.htmlBlock for block-level:

-- Inline widget (rendered within text flow)
widget.html(dom.span { class = "badge", "New" })
 
-- Block widget (gets its own block)
widget.htmlBlock(dom.table {
  dom.tr {
    dom.td { "Name" },
    dom.td { "Value" }
  }
})

Building tables dynamically

A common pattern is building HTML tables from query results:

local rows = {}
for page in query[[ from index.pages() limit 5 ]] do
  table.insert(rows, dom.tr {
    dom.td { "[[" .. page.name .. "]]" },
    dom.td { os.date("%Y-%m-%d", page.lastModified) }
  })
end
 
return widget.htmlBlock(dom.table {
  dom.thead {
    dom.tr {
      dom.td { "Page" },
      dom.td { "Modified" }
    }
  },
  dom.tbody(rows)
})

Embedding widgets inside DOM

Widget objects (like buttons from widgets.button) can be nested inside DOM elements:

dom.div {
  "Status: ",
  widgets.button("Refresh", function()
    editor.invokeCommand("System: Reload")
  end)
}

How it works

Under the hood, dom uses a Lua metatable so that any property access (e.g. dom.span) returns a constructor function. That function calls js.window.document.createElement(tag) and processes the spec table to set attributes, add event listeners, and append children.

See also: dom, widget, Space Lua/Widget