Your First Component
The easiest way to learn Ornata is to enhance a small piece of existing HTML with a clear component contract.
The markup
Section titled “The markup”<section data-disclosure> <button data-disclosure-button>Show details</button> <div hidden data-disclosure-panel> <p>Details ...</p> </div></section>The component
Section titled “The component”import { defineComponent } from 'ornata';
const Disclosure = defineComponent({ name: 'Disclosure', state: { open: { default: false, type: Boolean }, openLabel: { default: 'Hide details' }, closedLabel: { default: 'Show details' }, }, elements: { button: { query: '[data-disclosure-button]' }, panel: { query: '[data-disclosure-panel]' }, }, methods: { toggle() { this.state.open = !this.state.open; }, }, watch: { open({ newValue, oldValue, isInitial }) { if (isInitial) return;
console.log( `Disclosure's open state changed from ${oldValue} to ${newValue}` ); }, }, computed: { buttonLabel() { if (this.state.open) return this.state.openLabel; return this.state.closedLabel; }, }, render: { button() { return { text: this.computed.buttonLabel, attributes: { type: 'button', 'aria-expanded': String(this.state.open), }, events: { click: () => this.methods.toggle(), }, }; }, panel() { return { attributes: { hidden: !this.state.open, }, }; }, },});import { defineComponent } from 'ornata';
interface DisclosureState { open: boolean; openLabel: string; closedLabel: string;}
const Disclosure = defineComponent<{ state: DisclosureState; elements: { button: Element | null; panel: Element | null; }; methods: { toggle(): void; }; computed: { buttonLabel: string; };}>({ name: 'Disclosure', state: { open: { default: false, type: Boolean }, openLabel: { default: 'Hide details' }, closedLabel: { default: 'Show details' }, }, elements: { button: { query: '[data-disclosure-button]' }, panel: { query: '[data-disclosure-panel]' }, }, methods: { toggle() { this.state.open = !this.state.open; }, }, watch: { open({ newValue, oldValue, isInitial }) { if (isInitial) return;
console.log( `Disclosure's open state changed from ${oldValue} to ${newValue}` ); }, }, computed: { buttonLabel() { if (this.state.open) return this.state.openLabel; return this.state.closedLabel; }, }, render: { button() { return { text: this.computed.buttonLabel, attributes: { type: 'button', 'aria-expanded': String(this.state.open), }, events: { click: () => this.methods.toggle(), }, }; }, panel() { return { attributes: { hidden: !this.state.open, }, }; }, },});The mounting code
Section titled “The mounting code”const disclosure = Disclosure.mount('[data-disclosure]');How to read this component
Section titled “How to read this component”Each top-level option has a clear job:
statedefines reactive valueselementsfinds important DOM nodes inside the rootmethodsdefines reusable internal behaviorwatchreacts to state changescomputedderives values from staterenderapplies DOM updates to resolved elements
That separation is one of Ornata’s main strengths. It keeps the contract between markup, behavior, and state visible, which makes the component easier to reuse in larger HTML-first systems.
Continue to Component Anatomy for a deeper look at every option section.
If you want a fuller walkthrough of Ornata’s explicit typing patterns and TypeScript helpers, read TypeScript.