Skip to content

Element Safeguards

This example demonstrates how Ornata’s elements option can encode DOM expectations directly into a reusable component contract. This is useful for ensuring that the required HTML structure is present when a component first mounts.

If expected elements are missing, or if their required counts are not met, Ornata throws an error and reveals a mismatch between the HTML and the component’s contract. In this example, elements are resolved with more precise selectors and custom resolution logic, and they must satisfy explicit min and max requirements.

Open this demo in a new tab

<section data-tabs>
<div>
<button data-tab>Tab 1</button>
<button data-tab>Tab</button>
</div>
<section data-panel>Tab 1 content</section>
<section data-panel hidden>Tab 2 content</section>
</section>
import { defineComponent } from 'ornata';
export const Tabs = defineComponent({
name: 'Tabs',
state: {
activeIndex: { default: 0, type: Number },
},
elements: {
// 👇 Find the one direct div child expected to contain the tab buttons
tablist: {
query: ':scope > div:has(> [data-tab])',
min: 1,
max: 1,
},
// 👇 Find 2+ tab buttons that are direct children of a direct div child
tabs: {
queryAll: ':scope > div > [data-tab]',
min: 2,
},
// 👇 Find 2+ tab panels that are direct children
panels: {
queryAll: ':scope > [data-panel]',
min: 2,
},
},
render: {
tablist() {
return {
attributes: {
role: 'tablist',
},
};
},
tabs({ index }) {
const currentIndex = index ?? 0;
const isActive = currentIndex === this.state.activeIndex;
return {
attributes: {
type: 'button',
'aria-selected': String(isActive),
},
classes: {
'is-active': isActive,
},
events: {
click: () => {
this.state.activeIndex = currentIndex;
},
},
};
},
panels({ index }) {
return {
attributes: {
hidden: (index ?? 0) !== this.state.activeIndex,
},
};
},
},
});
  • resolve() gives you more precise control over how elements are retrieved from the DOM
  • min and max document a structural requirement directly in the component
  • the component’s DOM dependencies are visible in one place
  • render logic stays focused because the lookup work is already done

This is a good pattern for components like tabs, accordions, menus, and list-driven interactions.

For a render-focused version of this pattern, see List Rendering.