Skip to content

Templates & Slots

The data-ax-show directive reactively toggles the visibility of an element by modifying its inline CSS display property based on the evaluated expression.

<div data-ax-show="state.isVisible">This content is conditionally visible.</div>

When state.isVisible evaluates to a truthy value, the element is visible. When it evaluates to a falsy value, the element is hidden using display: none.

Unlike simple directives that hardcode display: block or remove the element from the DOM entirely, data-ax-show carefully conserves your layout styling:

  1. Boolean Conversion: The directive evaluates the expression and converts the result to a strict boolean (equivalent to !!value).
  2. Conserving Original Display: On initialization, before any styles are modified, Avenx-JS saves the element’s original CSS display property (such as flex, grid, inline-block, or default "") to an internal property (__originalDisplay) on the DOM element.
  3. Restoring Visibility:
    • When switching to true (visible), the element’s style.display is restored to its conserved __originalDisplay value.
    • When switching to false (hidden), style.display is set to 'none'.

Because __originalDisplay is conserved, toggling visibility will never break custom layout containers:

<!-- The inline 'display: flex' is saved to __originalDisplay on init -->
<div style="display: flex; gap: 10px;" data-ax-show="state.showToolbar">
<button>Action 1</button>
<button>Action 2</button>
</div>

When state.showToolbar becomes true, the element correctly reverts to display: flex instead of defaulting to block.

data-ax-show integrates seamlessly with Avenx-JS’s animation lifecycle when combined with <transition> wrappers or data-ax-transition attributes:

  • Enter Transitions: When switching from false to true, display is restored to __originalDisplay immediately, and the compiler triggers the -enter, -enter-active, and -enter-to CSS class sequence.
  • Leave Transitions: When switching from true to false, the element is not hidden immediately. Instead, the -leave, -leave-active, and -leave-to CSS classes are applied. An exit callback waits for the CSS transition or animation to finish before finally setting style.display = 'none'.

For a complete guide and code examples on animating visibility toggles, see the Transition Animations documentation.

5. Reactive Style Bindings (data-ax-style)

Section titled “5. Reactive Style Bindings (data-ax-style)”

Use the data-ax-style directive to dynamically apply inline CSS styles using a JavaScript object.

<p data-ax-style="{{ { color: state.textColor } }}">Dynamic text color</p>

When state.textColor changes, the element’s color style is updated automatically.

<div
data-ax-style="{{ {
color: state.textColor,
backgroundColor: state.backgroundColor,
fontSize: state.fontSize + 'px'
} }}"
>
Styled content
</div>
<span
data-ax-style="{{ {
color: state.isError ? 'red' : 'green',
fontWeight: state.isActive ? 'bold' : 'normal'
} }}"
>
Status
</span>

Using object syntax keeps templates more readable and maintainable than manually constructing inline style strings.

Use the data-ax-class directive to add or remove CSS classes reactively. Static class="…" attributes on the same element are preserved.

When the expression evaluates to a string, its space-separated tokens are applied as class names:

<div class="card" data-ax-class="state.themeClass">Themed card</div>
// e.g. in <state />
themeClass = 'theme-dark highlight';

When state.themeClass changes, previously applied dynamic classes from this directive are replaced with the new set. The static card class remains.

Pass an object whose truthy keys become class names (quote keys that are not valid identifiers):

<button class="btn" data-ax-class="{ active: state.isActive, 'text-large': state.isLarge, disabled: state.isDisabled }">
Action
</button>
Expression value Result
{ active: true, 'text-large': false } adds active; removes text-large if it was previously set by this directive
"theme-blue" applies theme-blue
"" / falsy clears dynamic classes from this directive

Note: Object and string forms are evaluated as template expressions in the component scope (same rules as other data-ax-* bindings). Prefer object form for multiple independent toggles.

Render lists, objects, sets, maps, or numeric ranges using the custom <@for> loop tag. Loop bodies are compiled to their own block – a skeleton parsed once for the life of the page and cloned per row – and reconciled by key:

<@for item in state.todos key="item.id">
<li>{{ item.title }}</li>
</@for>

The <@for> loop can iterate over various sources:

  1. Arrays: Iterates over array elements.
  2. Objects: Iterates over the object’s enumerable properties (entries). Use destructuring syntax to get [key, value]:
    <@for [key, value] in state.settings key="key">
    <li>{{ key }}: {{ value }}</li>
    </@for>
  3. Maps and Sets: Iterates in insertion order. For maps, destructuring works just like objects: [key, value].
  4. Numeric Ranges: Iterates N times from 0 to N-1.
    <!-- Renders 0, 1, 2, 3, 4 -->
    <@for n in 5 key="n">
    <li>Item #{{ n }}</li>
    </@for>

In addition to your item variable, every <@for> loop automatically injects a zero-indexed index variable into the template scope. You don’t need to declare it — ListManager adds it for you on each iteration:

<@for item in state.todos key="item.id">
<li class="{{ index % 2 === 0 ? 'even' : 'odd' }}">
{{ index + 1 }}. {{ item.title }}
</li>
</@for>

When the iterable source is empty (e.g. [], {}, or 0), you can display a fallback block using the <@empty> tag inside the loop:

<@for item in state.todos key="item.id">
<li>{{ item.title }}</li>
<@empty>
<li class="empty-state">No todos left!</li>
</@empty>
</@for>

Components can receive child HTML blocks using <slot> elements. Both default and named slots are fully supported.

<div class="card">
<div class="card-header">
<slot name="header">Default Header</slot>
</div>
<div class="card-body">
<slot></slot>
<!-- Default Slot -->
</div>
</div>
<Card>
<h2 slot="header">Special Title</h2>
<p>This content goes directly into the default slot!</p>
</Card>

If a component’s caller does not provide content for a given slot, Avenx-JS automatically falls back to rendering the default content defined inside that <slot> element in the component’s template. This applies to both named and default slots. For example, in the Card component above, if no slot="header" element is passed in, the header slot will render its fallback text, Default Header, instead of being left empty. This makes it easy to define sensible defaults for optional component content without requiring the caller to always supply every slot.

Checking Slot Presence (this.$slots.has())

Section titled “Checking Slot Presence (this.$slots.has())”

Components can determine whether a slot was provided by the parent using this.$slots.has(slotName).

if (this.$slots.has('default')) {
console.log('Default slot provided');
}
if (this.$slots.has('header')) {
console.log('Header slot provided');
}

If the slot is not provided, this.$slots.has() returns false, allowing components to conditionally render fallback content.

9. Passing Props to Child Components (data-props-*)

Section titled “9. Passing Props to Child Components (data-props-*)”

Custom child components can receive props from a parent page or component using the data-props-<propName> attribute syntax. The parser evaluates the attribute’s value as an expression in the parent’s scope and passes the resulting value into the child component as a prop.

<MyProfile data-props-user="state.currentUser" />

Here, data-props-user passes the value of state.currentUser from the parent scope into the MyProfile component as the user prop. Inside the child component, the prop is accessed via this.props.user:

src/components/my-profile/my-profile.component.js
<div class="profile">
<p>Welcome, {{ this.props.user.name }}</p>
</div>

Note: The portion of the attribute name after data-props- becomes the prop name on the child (e.g. data-props-user → props.user). Multiple props can be passed by adding additional data-props-* attributes:

<MyProfile data-props-user="state.currentUser" data-props-isAdmin="state.isAdmin" />

A plain attribute whose value interpolates does the same thing, and reads more like ordinary HTML:

<MyProfile user="{{ currentUser }}" isAdmin="{{ isAdmin }}" />

The two differ only in how the value is read. data-props-user="currentUser" is an expression, so a literal string needs its own quotes (data-props-title="'Account Overview'"). user="{{ currentUser }}" is an interpolation, so an unbraced value is a literal string (title="Account Overview"). Both deliver the value to this.props.<name>, and both are reactive: the child updates when the state behind the value changes.

Use whichever reads better. The generated components and the Quick Start use the plain form.

Avenx-JS natively supports rendering SVG elements inside templates. During template cloning and patching, the framework automatically preserves the correct SVG namespace (http://www.w3.org/2000/svg), ensuring that SVG graphics render correctly in the browser. This includes nested SVG elements such as <rect>, <circle>, <path>, and other SVG-specific tags. Even when templates are parsed using DOMParser, Avenx-JS automatically transitions SVG elements into the correct namespace during patching and cloning, so no additional configuration or manual namespace handling is required.

<svg width="200" height="200" viewBox="0 0 200 200">
<rect x="20" y="20" width="160" height="160" rx="12" fill="#4F46E5" />
<circle cx="100" cy="100" r="50" fill="#22C55E" />
<path d="M50 150 L100 50 L150 150 Z" fill="#FACC15" />
</svg>

11. Static Subtree Optimization & Reconciliation Markers (data-ax-static, data-ax-skip, data-ax-key)

Section titled “11. Static Subtree Optimization & Reconciliation Markers (data-ax-static, data-ax-skip, data-ax-key)”

To achieve maximum rendering performance and eliminate Virtual DOM overhead, the Avenx-JS compiler performs static template analysis during component compilation. It decorates template subtrees with internal reconciliation attributes that instruct DomPatcher and ListManager to bypass unnecessary DOM diffing.

1. Static Subtree Marker (data-ax-static="true")

Section titled “1. Static Subtree Marker (data-ax-static="true")”

During component parsing (ComponentParser.optimizeStaticSubtrees()), Avenx-JS walks the HTML template tree and identifies subtrees that contain no dynamic interpolations ({{ }} or {{{ }}}), directives (data-ax-*), or nested components. The root node of each static subtree is automatically decorated with data-ax-static="true".

During reactive state updates, when DomPatcher encounters an element with data-ax-static="true", it immediately bypasses attribute patching and child node diffing for that entire subtree:

// Inside DomPatcher.#patchNode
if (!isPatchRoot && oldNode.nodeType === Node.ELEMENT_NODE && oldNode.hasAttribute('data-ax-static')) {
return; // Skip diffing for static subtree!
}

Source SFC Template:

<div class="user-card">
<!-- Static Subtree: No interpolations or directives -->
<header class="card-header">
<h3>User Profile</h3>
<p>Account Overview & Settings</p>
</header>
<!-- Dynamic Content -->
<div class="card-body">
<p>Welcome, {{ state.username }}</p>
</div>
</div>

Compiled Template Output:

<div class="user-card">
<header class="card-header" data-ax-static="true">
<h3>User Profile</h3>
<p>Account Overview & Settings</p>
</header>
<div class="card-body">
<p>Welcome, {{ state.username }}</p>
</div>
</div>

Because <header> is tagged with data-ax-static="true", any update to state.username re-diffs only <div class="card-body">, while <header> and its children are skipped entirely during DOM patching.

2. Directive Bypass Marker (data-ax-skip="true")

Section titled “2. Directive Bypass Marker (data-ax-skip="true")”

Directives like data-ax-html or custom element directives can instruct DomPatcher to skip recursive child node diffing (skipChildren = true). This prevents DomPatcher from overwriting imperatively managed DOM structures or custom inner HTML trees.

3. Reconciliation Key Marker (data-ax-key / key)

Section titled “3. Reconciliation Key Marker (data-ax-key / key)”

During <@for> list rendering, ListManager assigns or reads data-ax-key="value" to track element identities across reactive updates:

  • Node Reuse & Reordering: During list reconciliations, DomPatcher matches elements by data-ax-key. Reordered array items are moved in the DOM rather than unmounted and re-created.
  • Node Isolation: Ensures component state and input focus inside list items are preserved cleanly during array mutations.