Skip to content

Build a Disclosure

This tutorial walks through a small disclosure component from plain HTML to reusable interactive behavior.

<section data-disclosure>
<button
type="button"
aria-expanded="false"
data-disclosure-button
>
More details
</button>
<div hidden data-disclosure-panel>
This content exists in the page markup from the start.
</div>
</section>

This is the important starting point: the content is already in the document.

state: {
open: { default: false, type: Boolean },
}

The disclosure only needs one reactive property: open.

elements: {
button: { query: "[data-disclosure-button]" },
panel: { query: "[data-disclosure-panel]" },
}
methods: {
toggle() {
this.state.open = !this.state.open;
},
}
render: {
button() {
return {
attributes: {
"aria-expanded": String(this.state.open),
},
events: {
click: () => this.methods.toggle(),
},
};
},
panel() {
return {
attributes: {
hidden: !this.state.open,
},
};
},
}
import { defineComponent } from "ornata";
export const Disclosure = defineComponent({
name: "Disclosure",
state: {
open: { default: false, type: Boolean },
},
elements: {
button: { query: "[data-disclosure-button]" },
panel: { query: "[data-disclosure-panel]" },
},
methods: {
toggle() {
this.state.open = !this.state.open;
},
},
render: {
button() {
return {
attributes: {
"aria-expanded": String(this.state.open),
},
events: {
click: () => this.methods.toggle(),
},
};
},
panel() {
return {
attributes: {
hidden: !this.state.open,
},
};
},
},
});
Disclosure.mount("[data-disclosure]");
  • the HTML stays meaningful on its own
  • state drives interaction
  • render output updates existing DOM
  • methods keep imperative logic tidy

That is the core Ornata workflow in a very small example.