List Rendering
This example shows how index helps when repeated HTML elements share one piece of state but still need individual DOM updates.
In this example, a group of related elements is retrieved from the DOM, but each one needs to respond differently to the component’s current state. For example, only one step can be active at a time, while the others may be complete or still upcoming. The framework-provided index value in the render context makes it possible to identify which element in the list is being rendered and apply the correct output to each one.
Live Demo
Section titled “Live Demo”<section data-stepper> <ol> <li data-step>Step 1</li> <li data-step>Step 2</li> <li data-step>Step 3</li> </ol> <div> <button data-prev>Previous</button> <button data-next>Next</button> </div></section>Component
Section titled “Component”import { defineComponent } from 'ornata';
export const Stepper = defineComponent({ name: 'Stepper', state: { activeIndex: { default: 0, type: Number }, }, elements: { steps: { queryAll: '[data-step]', min: 2 }, prev: { query: '[data-prev]' }, next: { query: '[data-next]' }, }, methods: { previous() { this.state.activeIndex = Math.max(0, this.state.activeIndex - 1); }, next() { this.state.activeIndex = Math.min( this.elements.steps.length - 1, this.state.activeIndex + 1 ); }, }, render: { // 👇 Use the element's index in the list to decide how it should render. steps({ index }) { const currentIndex = index ?? 0; const isActive = currentIndex === this.state.activeIndex; const isComplete = currentIndex < this.state.activeIndex;
return { attributes: { 'aria-current': isActive ? 'step' : null, }, classes: { 'is-active': isActive, 'is-complete': isComplete, }, }; }, prev() { return { attributes: { type: 'button', disabled: this.state.activeIndex === 0, }, events: { click: () => this.methods.previous(), }, }; }, next() { return { attributes: { type: 'button', disabled: this.state.activeIndex === this.elements.steps.length - 1, }, events: { click: () => this.methods.next(), }, }; }, },});import { defineComponent } from 'ornata';
interface StepperState { activeIndex: number;}
export const Stepper = defineComponent<{ state: StepperState; elements: { steps: HTMLLIElement[]; prev: HTMLButtonElement | null; next: HTMLButtonElement | null; }; methods: { previous(): void; next(): void; };}>({ name: 'Stepper', state: { activeIndex: { default: 0, type: Number }, }, elements: { steps: { queryAll: '[data-step]', min: 2 }, prev: { query: '[data-prev]' }, next: { query: '[data-next]' }, }, methods: { previous() { this.state.activeIndex = Math.max(0, this.state.activeIndex - 1); }, next() { this.state.activeIndex = Math.min( this.elements.steps.length - 1, this.state.activeIndex + 1 ); }, }, render: { // 👇 Use the element's index in the list to decide how it should render. steps({ index }) { const currentIndex = index ?? 0; const isActive = currentIndex === this.state.activeIndex; const isComplete = currentIndex < this.state.activeIndex;
return { attributes: { 'aria-current': isActive ? 'step' : null, }, classes: { 'is-active': isActive, 'is-complete': isComplete, }, }; }, prev() { return { attributes: { type: 'button', disabled: this.state.activeIndex === 0, }, events: { click: () => this.methods.previous(), }, }; }, next() { return { attributes: { type: 'button', disabled: this.state.activeIndex === this.elements.steps.length - 1, }, events: { click: () => this.methods.next(), }, }; }, },});Why index matters
Section titled “Why index matters”Without index, each repeated element would need more custom plumbing.
With it, each step can respond to shared component state while still rendering individually as active, complete, or upcoming.