Template Overview
Udodi templates define component markup using HTML and a small declarative directive language.
A template describes the DOM structure of a component. Udodi directives, identified by the @ prefix, connect that markup to component state, events, conditional rendering, lists, attributes, styling, and DOM references.
Templates are designed to remain close to HTML while providing the reactive behavior needed to build interactive interfaces.
Templates in Components
A component defines its markup through the template option.
The html helper is the recommended way to write templates:
import { createComponent, html, render } from "udodi";
const Greeting = createComponent({
name: "Greeting",
state() {
return {
userName: "Attamah",
};
},
template: html`
<div>
<p>Hello, <span @text="userName"></span></p>
</div>
`,
});
render(Greeting(), "#app");The template remains ordinary HTML, with Udodi directives added where dynamic behavior is required.
A component template must produce exactly one root element when mounted.
Directives
Udodi directives are HTML attributes whose names begin with @.
For example:
<button @on="click=save">
Save
</button>The directive connects the DOM event to the component's save method.
Udodi provides directives for common UI operations:
| Directive | Purpose |
|---|---|
@text | Bind an element's text content |
@bind | Bind form control values to component data |
@on | Handle DOM events |
@ref | Register a DOM element reference |
@if | Conditionally render content |
@show | Toggle element visibility |
@for | Render repeated content |
@class | Manage element classes |
@style | Manage inline styles |
@attr | Bind element attributes |
@teleport | Render content at another DOM target |
Form-specific directives such as @form, @validate, and @submit are documented under Forms.
Template Expressions
Directive values are evaluated against the component's template context.
This allows templates to access values exposed by the component, including state, computed values, methods, props, and standard library helpers provided by the template runtime.
For example:
const Greeting = createComponent({
name: "Greeting",
state() {
return {
userName: "attamah",
};
},
template: html`
<p @text="userName | capitalise"></p>
`,
});In this example:
userNameresolves to the component's state value.capitaliseresolves to a standard library helper.- The
|pipeline operator passes the value ofuserNamethroughcapitalise. @textrenders the resulting value as the element's text content.
The expression:
userName | capitalisetherefore reads the userName value and transforms it before rendering.
The template DSL supports paths, function calls, pipelines, conditional expressions, literals, and directive-specific binding syntax.
More complex expression syntax is documented in Template DSL.
Reactive Bindings
Directives that read reactive values can update the DOM when those values change.
For example:
const Counter = createComponent({
name: "Counter",
state() {
return {
count: 0,
};
},
template: html`
<span @text="count"></span>
`,
});When count changes, the @text binding updates the element's text content.
This is part of Udodi's fine-grained reactive model. A reactive binding tracks the values it reads rather than requiring the entire component template to be rendered again.
See Reactivity for the underlying reactive system.
Text Content
Use @text when an element's text content should come from a template expression:
<span @text="userName"></span>The value is evaluated and applied to the element's textContent.
For static text, use normal HTML:
<span>Hello</span>Use @text when the content depends on component data.
See @text.
Form Binding
Use @bind when a form control should stay synchronized with a component value:
<input @bind="userName" />The binding connects the control to the corresponding component path.
Nested paths can also be used:
<input @bind="user.name" />The runtime handles the appropriate value behavior for supported form controls, including text inputs, checkboxes, and radio buttons.
See @bind for the supported control types and binding behavior.
Events
Use @on to connect DOM events to component methods or other supported handler expressions.
For example:
methods: {
save() {
// application logic
},
},<button @on="click=save">
Save
</button>The event expression identifies the DOM event and the handler that should be invoked.
Multiple Event Bindings
A single @on directive can contain multiple event bindings:
<button @on="click=save mouseover=highlight">
Save
</button>Each binding defines an independent event-handler relationship.
Modifiers can also be specified independently:
<form @on="submit.prevent=save keydown.stop=handleKey"></form>@on handlers can use the supported function-call syntax and handler arguments:
<button @on="click=save:message">
Save
</button>See @on for the complete event syntax, modifiers, and handler behavior.
Conditional Rendering
Udodi provides @if, @elseif, and @else for conditional rendering.
<div @if="loading">
Loading...
</div>
<div @elseif="error">
Unable to load the data.
</div>
<div @else>
Content
</div>The conditional branches form a single chain. The runtime evaluates the conditions and renders the branch whose condition matches.
Use conditional rendering when the presence of the content itself should depend on application state.
See @if.
Conditional Visibility
Use @show when an element should remain mounted while its visibility changes.
<div @show="menuOpen">
Menu
</div>@show controls the element's hidden state rather than conditionally creating and destroying the element.
This makes @show useful when the DOM element should remain available while its visibility changes.
See @show.
Rendering Lists
Use @for to render repeated content from a collection.
Supported forms:
<!-- item and collection -->
<li @for="item items">
<span @text="item"></span>
</li>
<!-- item, index, and collection -->
<li @for="item index items">
<span @text="index"></span>
<span @text="item"></span>
</li>item and optional index are loop-local names. items is an expression that evaluates to a collection supported by @for.
An optional @key can provide stable identity for reconciliation. List-specific syntax and behavior are documented in @for.
Classes and Styles
Use @class and @style when classes or inline styles need to respond to template values.
Classes
@class supports static class values as well as dynamic expressions, including conditional expressions and expressions that produce class values.
<!-- static -->
<div @class="'panel elevated'"></div>
<!-- dynamic -->
<div
class="panel"
@class="isActive=>'active' sizeClass"
>
Content
</div>In the dynamic example:
isActive=>'active'conditionally contributes theactiveclass.sizeClassresolves a class value from the component context.
Class expressions can produce supported class values such as strings or arrays.
Inline Styles
@style supports static CSS declaration strings and dynamic expressions that produce supported style values.
<!-- static -->
<div @style="'opacity:0.5;color:red'"></div>
<!-- dynamic -->
<div @style="boxStyles"></div>Dynamic style values can be provided in supported forms such as CSS strings, style objects, or arrays of style pairs.
Normal HTML attributes remain appropriate for static values:
<div class="panel">
Content
</div>For component-owned CSS, use the component's style option instead:
style: css`
.panel {
padding: 1rem;
}
`,Use:
stylefor component CSS@classfor reactive class changes@stylefor reactive inline style changes
See @class, @style, and Component Styles.
Attributes
Use @attr when HTML attributes need to be dynamically controlled by template expressions.
Bindings are space-separated name=expression pairs:
<input @attr="disabled=isDisabled=>'disabled' title=tooltip" />
<a @attr="href=url aria-label=label"></a>@attr supports one or more attribute bindings. Each binding is evaluated independently and can return a string, number, and boolean.
Returning null, undefined, or "" removes the attribute contributed by that binding. If the attribute originally existed on the element, its original value is restored.
Static attributes can remain on the element alongside @attr:
<input class="field" @attr="disabled=isDisabled=>'disabled'" />Use ordinary HTML attributes for values that do not need to be controlled dynamically:
<input disabled class="field" />See @attr.
DOM References
Use @ref when component code needs direct access to a DOM element.
<input @ref="query" />The registered element can then be accessed through the component context:
methods: {
focusQuery() {
this.refs.query?.focus();
},
},Refs are useful for imperative DOM operations such as focusing, measuring, or scrolling.
Prefer declarative directives when the behavior can be expressed directly in the template.
See @ref.
Teleporting Content
Use @teleport when content needs to be rendered into another DOM target.
This is useful for UI that needs to escape its normal DOM position, such as layered or overlay content.
See @teleport.
For higher-level modal and layered UI behavior, see Overlay.
Nested Components
Udodi components can be composed inside other component templates.
A parent component can render child components as part of its template. Each component maintains its own state, context, lifecycle, and other component configuration.
Conceptually:
Parent component
│
└── template
│
├── parent DOM
│
└── child component
│
├── child state
├── child context
├── child template
└── child lifecycleComponent inputs and live property bindings are documented in Props.
Template and Reactivity
Templates are integrated directly with Udodi's fine-grained reactivity system.
A directive that depends on reactive data can establish a reactive binding:
component state
│
▼
template expression
│
▼
directive binding
│
▼
DOMWhen the relevant reactive value changes, the binding can update the affected DOM rather than requiring the complete component to be rendered again.
This is one of the main differences between Udodi's template model and component systems based primarily on whole-component re-rendering.
See Reactivity Overview.
Template Lifecycle
At a high level, mounting a component template involves:
Component
│
▼
Template
│
▼
DOM creation
│
▼
Directive binding
│
▼
Nested component mounting
│
▼
Mounted DOM
│
▼
Reactive updatesThe template declares the desired structure and behavior. The runtime performs the DOM operations and establishes the bindings required to make that structure live.
When the component is unmounted, the runtime removes the resources associated with the mounted component, including directive and reactive cleanup.
See Lifecycle.
Template Rules
Single Root Element
A component template must produce exactly one root element when mounted.
Valid:
template: () => html`
<main>
<h1>Hello</h1>
<p>Content</p>
</main>
`,Invalid:
template: () => html`
<h1>Hello</h1>
<p>Content</p>
`,Wrap multiple top-level elements in a single parent element.
HTML-First Markup
Templates use HTML as their primary markup language.
Udodi extends HTML with @ directives instead of requiring a separate template component syntax.
Declarative Behavior
Prefer expressing UI behavior through directives and component methods.
Use direct DOM access through @ref when imperative browser APIs are actually required.
Directive-Specific Syntax
The shared template DSL provides common expression forms, but individual directives may accept different value shapes.
For example, @attr and @on support multiple named bindings:
<a @attr="href=url title=tooltip"></a>
<button @on="click=save mouseover=highlight"></button>Other directives normally consume a single expression:
<span @text="user.name"></span>Likewise, conditional expressions can contain another supported expression on their right-hand side:
isActive => activeClass
isActive => getClass:sizeDo not assume that syntax valid for one directive is automatically valid for another.
See Template DSL for the shared expression language and directive expression rules.
Template DSL
The template DSL defines the expression syntax used by directive values.
For example:
<span @text="name"></span>
<button @on="click=save"></button>
<div @show="visible"></div>
<div @class="isActive=>'active' sizeClass"></div>The DSL supports:
- Paths such as
user.name - Function calls such as
save:message - Pipelines such as
userName | capitalise - Conditional expressions such as
isActive=>'active' - Literal values such as
'Hello',42, andtrue - Directive-specific bindings such as
href=url - Multiple bindings where the directive supports them
The individual directives build on this expression system, but each directive has its own value semantics and syntax.
Do not assume that the syntax of one directive applies to another.
See Template DSL for the complete expression language and directive expression rules.
Summary
Udodi templates combine standard HTML with a focused set of @ directives.
Use templates to:
- describe component markup
- bind reactive values to DOM content and properties
- handle DOM events, including multiple event bindings
- conditionally render content
- render collections
- manage classes, styles, and attributes
- access DOM elements when imperative access is required
- compose components
- express dynamic values through the template DSL