> ## Documentation Index
> Fetch the complete documentation index at: https://aerojs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Structural reactivity

> Reactive if/else, keyed for loops, and switch — compile-time directives that update at runtime when state changes.

## The problem

Build-time `if` and `for` directives render once at compile time. Showing different branches or lists that change after user interaction needs either client-side templating or manual DOM create/destroy.

## How Aero helps

With `<script is:state>`, structural directives reconcile against live state. The compiler still lowers templates to HTML, but the reactivity mount patches branches and list rows when bindings change.

Build-time and runtime structural directives use the same syntax. The difference is the presence of a state script and `reactivity: true`.

## Reactive conditionals

`if`, `else-if`, and `else` choose one branch based on a state expression:

```html theme={"theme":"github-dark-default"}
<script is:state>
	let n = 0
</script>

<div class="card">
	<p if="{ n > 0 }">Positive</p>
	<p else-if="{ n < 0 }">Negative</p>
	<p else>Zero</p>
</div>

<button on:click="{ n++ }">+</button>
<button on:click="{ n-- }">−</button>
```

## Reactive `switch`

Match a state value against `case` branches, with `default` as fallback:

```html theme={"theme":"github-dark-default"}
<script is:state>
	let status = 'loading'

	function cycleStatus() {
		status = status === 'loading' ? 'error' : status === 'error' ? 'ready' : 'loading'
	}
</script>

<div switch="{ status }">
	<p case="loading">Loading…</p>
	<p case="error">Something went wrong</p>
	<p default>Ready</p>
</div>

<button on:click="{ cycleStatus() }">Cycle status</button>
```

## Keyed `for` loops

Reactive lists need a stable `key` so Aero can move or reuse DOM nodes instead of re-rendering the whole list:

```html theme={"theme":"github-dark-default"}
<script is:state>
	let items = [{ id: 'a' }, { id: 'b' }]

	function addItem() {
		items = [...items, { id: crypto.randomUUID() }]
	}
</script>

<ul>
	<li
		for="{ const item of items }"
		key="{ item.id }"
		text="{ item.id }"></li>
</ul>

<button on:click="{ addItem() }">Add</button>
```

### In-place list mutation

Keyed `for` also reconciles when you mutate the same array, `Set`, or `Map` in place:

```html theme={"theme":"github-dark-default"}
<script is:state>
	let numbersArray = [1, 2, 3]
	let numbersSet = new Set([1, 2, 3])
	let numbersMap = new Map([[1, 'one'], [2, 'two']])
</script>

<button on:click="{ numbersArray.push(11) }">Add to array</button>
<button on:click="{ numbersSet.add(11) }">Add to set</button>
<button on:click="{ numbersMap.set(11, 'eleven') }">Add to map</button>

<div for="{ const n of numbersArray }" key="{ n }">{ n }</div>
<div for="{ const n of numbersSet }" key="{ n }">{ n }</div>
<div for="{ const [key, value] of numbersMap }" key="{ key }">{ key }: { value }</div>
```

Immutable replacement (`items = [...items, next]`) and in-place mutation (`items.push(next)`) both notify the binding. Prefer immutable updates when you use view transitions or need a new reference for derived state.

### Destructuring in `for`

The loop binding supports destructuring patterns:

```html theme={"theme":"github-dark-default"}
<li for="{ const { id, label } of items }" key="{ id }">{ label }</li>
```

### Requirements

* `key` must evaluate to a string or number per row.
* Duplicate keys in one reconciliation pass throw at runtime.
* The iterable may be an array, `Set`, `Map`, any object with `[Symbol.iterator]`, or `null` / `undefined` (treated as empty).
* `Map` rows iterate as `[key, value]` entry pairs — use destructuring in the loop binding.

<Note>
  Keyed reconciliation preserves focus and element state inside rows when items reorder. Pair with [view transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) in client scripts for animated list updates.
</Note>

## Output fidelity

Reactive bindings anchor to **authored markup** instead of injecting wrapper elements:

* **Switch** — `data-aero-switch` on the element with `switch="{ … }"`.
* **Keyed `for`** — `data-aero-for` on the parent container when the loop row is the only element child (e.g. `<ul data-aero-for>` with `<li for>` rows).
* **`if` chains** — `data-aero-if` on a parent wrapper when the chain is its only content; otherwise HTML comment boundaries (`<!-- aero:if:N -->` … `<!-- /aero:if:N -->`).
* **Text interpolation** — `data-aero-text` on an element whose sole child is reactive text; mixed content uses comment boundaries. Prefer the explicit `text="{ expr }"` attribute when you need a marker on a specific element.

This keeps selectors like `ul > li` and `:nth-child` predictable in the live DOM.
