Skip to content

Elements

One of Ornata’s nicest features is that DOM references are part of the component contract.

Instead of scattering querySelector() calls across event handlers and render code, you define the important elements once in elements, then let Ornata resolve and validate them during mount.

This approach gives you a few benefits at once:

  • lookups are scoped to the component root
  • each element definition communicates intent
  • structural mistakes are caught earlier
  • duplicate element references can be caught during mount
  • render and method code can focus on behavior instead of DOM plumbing

Every query and queryAll lookup happens inside the component root.

elements: {
button: { query: "[data-disclosure-button]" },
items: { queryAll: "[data-item]" },
}

That keeps component lookups local and reduces the chance of accidentally grabbing matching elements from elsewhere on the page.

Ornata supports four ways to resolve an element property:

  • query for a single element
  • queryAll for a list of elements
  • create to create a new element
  • resolve for custom logic
elements: {
panel: { query: "[data-panel]" },
items: { queryAll: "[data-item]" },
status: { create: "output" },
input: {
resolve(root) {
return root.querySelector("[data-filter-input]");
},
},
}

When your markup has structural expectations, make them explicit.

elements: {
items: {
queryAll: "[data-tab]",
min: 2,
},
button: {
query: "[data-submit]",
max: 1,
},
}

This is especially useful for components like tabs, menus, accordions, and lists where the DOM shape matters.

Use resolve() when you need stronger control

Section titled “Use resolve() when you need stronger control”

resolve() is the escape hatch for cases where a plain selector is not enough.

It is a good fit when you want to:

  • apply custom filtering logic
  • implement fallback logic
  • return a specific element array shape
  • create non-HTML elements such as SVG elements
  • store references to elements outside the root
  • return a narrower DOM type like HTMLInputElement | null
elements: {
input: {
resolve(root) {
return root.querySelector("[data-search-input]");
},
},
}

Think of elements as a DOM schema for the component.

It tells future readers:

  • what nodes the component cares about
  • how those nodes are found
  • how many are expected
  • whether any custom resolution rules exist

That makes the component easier to trust and easier to change.