---
title: Control flow
description: How @if, @switch, and keyed @for add, remove, and move real elements on the page.
sidebar: { label: Control flow }
---

Last page, a value decided if an attribute exists. Blocks decide if whole elements exist.

I expected `@for` to rebuild the whole list on every change. It does not, and the `key` is why.

`@if`, `@switch`, and `@for` are blocks inside the markup. A branch adds and removes real elements. A keyed `@for` ties each row to its key, so a reorder moves the rows that already exist.

Click **Shuffle**, then **Add**, then **Remove first**.

<CompForListFigure />

Notice that the row with key `A` keeps element 1 until you remove it.

## Branches

```tsrx Counter.tsrx
import { computed, state } from '@markless/core';

export default function Counter() @{
	let count = state(0);
	const many = computed(() => count > 3);

	<section>
		<button onClick={() => count++}>Count {count}</button>
		@if (many) {
			<p>That is a lot of clicks.</p>
		} @else {
			<p>Keep going.</p>
		}
	</section>
}
```

The test inside `@if ( )` is a plain read: a state or a `computed()`. [Computed values](/state/computed) get their own page later. Write `@else` with the `@`. A plain `else` fails the build.

For more than two cases, use `@switch`:

```tsrx Light.tsrx
import { state } from '@markless/core';

export default function Light() @{
	let color = state('red');

	<div>
		<button onClick={() => (color = color === 'red' ? 'green' : 'red')}>Switch</button>
		@switch (color) {
			@case 'red': { <p>Stop.</p> }
			@case 'green': { <p>Go.</p> }
			@default: { <p>Wait.</p> }
		}
	</div>
}
```

## Lists

```tsrx Todos.tsrx
import { state } from '@markless/core';

export default function Todos() @{
	let items = state([{ id: 1, name: 'Milk' }]);

	<ul>
		@for (const item of items; index i; key item.id) {
			<li>{i + 1}. {item.name}</li>
		} @empty {
			<li>No items yet</li>
		}
	</ul>
}
```

`key` names what makes a row unique. `index i` gives the row its position. `@empty` shows when the list has no items.

Create state at the top of the body, not inside a block. A `state()` inside `@if` fails with `MARKLESS_STATE_CREATION_SITE_UNSTABLE`.

:::warning[Why does my @if never change?]
A test such as `@if (count > 3)` shows its first answer only. The compiler warns with `MARKLESS_TEMPLATE_EXPRESSION_STATIC`. Move the test into a `computed()` and put that name in the `@if`.
:::

:::warning[Why does my @for need a key?]
A `@for` over state without a `key` fails with `MARKLESS_REPEAT_KEY_REQUIRED`. A key by position, such as `key i`, warns with `MARKLESS_REPEAT_KEY_IS_INDEX`. Then a row follows its slot, not its data. Key by a stable id from your data.
:::

**Coming from another framework?**

`@for` with `key` plays the part of `items.map()` with a `key` prop in JSX. `@if` and `@switch` replace `&&` and `? :` around markup.

**Next:** How does one component pass data to another? [Props and children →](/components/props-and-children)
