Skip to content

Component Anatomy

defineComponent() is the main authoring API in Ornata.

It is where you describe how a reusable component fits into existing HTML, what it expects from the DOM, and how it reacts over time.

const Component = defineComponent({
name: "Example",
root: {},
state: {},
elements: {},
lifecycle: {},
watch: {},
methods: {},
computed: {},
data: {},
render: {},
});

You will not use every section in every component, but each one has a clear role.

name sets the display name used in debugging and error reporting.

If you omit it, Ornata falls back to "UnnamedComponent".

Use root.matches when you want to validate that the mounted root matches a selector.

root: {
matches: "[data-counter]",
}

state defines reactive properties for the component.

Each property supports:

  • default
  • type
  • parse
  • private
  • readonly
state: {
count: { default: 0, type: Number },
label: { default: "Clicks", readonly: true },
}

Why state is a bigger feature than it looks

Section titled “Why state is a bigger feature than it looks”

State in Ornata is designed for HTML-first environments:

  • defaults can be declared in the component
  • initial values can come from root HTML data-* attributes
  • mount-time initialState can override both
  • writes trigger the component update flow automatically
  • public access can be restricted with private and readonly

For a focused walkthrough, see State.

elements resolves important DOM references within the root element.

This is one of Ornata’s most valuable features because it turns DOM lookups into a defined part of the component contract instead of leaving them scattered across the implementation.

Each property can use:

  • query
  • queryAll
  • create
  • resolve
  • min
  • max
elements: {
button: { query: "[data-count-button]" },
items: { queryAll: "[data-item]" },
}

Element resolution in Ornata comes with a few built-in safeguards:

  • lookups are scoped to the component root
  • min and max can validate expected element counts
  • duplicate element references are detected
  • resolve() gives you an explicit escape hatch for custom logic

That makes elements a strong fit for component APIs that depend on stable DOM structure.

elements: {
tabs: {
queryAll: "[data-tab]",
min: 2,
},
panels: {
queryAll: "[data-panel]",
min: 2,
},
}

For a dedicated walkthrough, see Safe DOM References.

methods defines internal reusable actions.

Methods are bound to the internal instance, so they can safely use this.state, this.elements, this.computed, and this.data.

methods: {
increment() {
this.state.count += 1;
},
}

Methods are where your component’s named actions live.

They are especially useful for keeping render callbacks small and for centralizing event-driven logic that would otherwise be duplicated inline.

For a focused walkthrough, see Methods.

computed derives values from state changes.

Computed callbacks receive a context object with:

  • type
  • currentValue
  • changedProperty
computed: {
total() {
return this.state.count;
},
}

watch reacts to state changes.

Watch callbacks receive:

  • type
  • newValue
  • oldValue
  • isInitial
watch: {
count({ isInitial, newValue }) {
if (!isInitial) {
console.log("Updated:", newValue);
}
},
}

data stores additional user-defined values on the internal instance.

Use it for values that do not need to be reactive.

data: {
analyticsKey: "counter",
}

data is the right place for persistent internal values that should survive for the life of the component instance without triggering updates.

Common examples include timer IDs, observer instances, caches, and third-party integration objects.

For a focused walkthrough, see Data.

render maps resolved elements to DOM updates.

Each render callback returns a RenderOptions object with keys such as:

  • style
  • classes
  • attributes
  • dataset
  • events
  • html
  • text
render: {
value() {
return {
text: String(this.state.count),
};
},
}

lifecycle.mount runs once when the component mounts.

lifecycle.unmount runs once when the component is disposed or unmounted.

lifecycle: {
mount() {
console.log("Mounted");
},
unmount() {
console.log("Cleaned up");
},
}

Lifecycle hooks are best for setup and cleanup work rather than DOM output.

For a deeper walkthrough, see Lifecycle.