---
url: /concepts/template-syntax.md
---
# Template Syntax

## Static properties

You can use static properties in your components.

```html
<Text text="Hello World" />
<Text text='Single quotes are supported' />
<Text text="" />
```

Static values must be quoted. Use braces for JavaScript values, for example
`size={14}` rather than `size=14`.

## Binding properties

You can use reactive properties in your components. Use the `{}` syntax to bind a property.

```html
<Text text={text} />

<script>
  const text = 'Hello World';
</script>
```

::: tip
If the attribute name is the same as the variable, you can simplify:

```html
<Text text />

<script>
  const text = 'Hello World';
</script>
```

:::

::: warning Grammar
Literal `@` prefixes inside expressions are no longer supported. Only template directives such as `@if`, `@else`, and `@for` use the `@` prefix.
:::

Literal `@` characters are supported in text content, such as
`<p>contact@example.com</p>`.

### Spread properties

Both CanvasEngine's concise spread syntax and the JSX-style form are accepted:

```html
<Container ...props />
<Container {...props} />
<Container {...getProps()?.container} />
```

Use the form with braces for complex JavaScript expressions.

### Comments

HTML and JSX-style comments can be used between template children and between
conditional branches:

```html
<Container>
  <!-- HTML comment -->
  {/* JSX-style comment */}
</Container>
```

### Expressions and computed

The compiler only wraps expressions containing a function call in `computed`. Simple expressions stay as-is.

```html
<Text text={1 + 1} />
<Text text={nb() + 1} />
```

Compiles to:

```js
h(Text, { text: 1 + 1 })
h(Text, { text: computed(() => nb() + 1) })
```

Nested object and array values support the same JavaScript expressions. Call signals explicitly when a nested value must be reactive:

```html
<Button
  style={{
    backgroundColor: {
      normal: selected() ? '#3f3f46' : '#27272a',
      hover: '#52525b'
    },
    text: { color: statusColor() }
  }}
/>
```

Because this object contains function calls, the compiler wraps the complete object in `computed`. An object containing only static values stays unchanged.

## Event listeners

You can use event listeners in your components

```html
<Rect width={100} height={100} color="red" click={click} />

<script>
  const click = () => {
    console.log("clicked");
  };
</script>
```

::: tip
If the attribute name is the same as the variable, you can simplify:

```html
<Rect width={100} height={100} color="red" click />

<script>
  const click = () => {
    console.log("clicked");
  };
</script>
```

:::

::: tip
You can use arrow functions in your event listeners.

```html
<Rect width={100} height={100} color="red" click={() => console.log("clicked")} />
```

:::

> Use PixiJS events (https://pixijs.download/release/docs/scene.Container.html)

## Conditional rendering

You can use the `@if` directive to conditionally render a component.

```angular-html
<Container>
   @if (show) {
     <Text text="Hello World" />
   }
</Container>

<script>
  const show = true;
</script>
```

`@if` can also be used as the root template structure.

### @if/@else if/@else

You can use `@else if` and `@else` to create more complex conditional logic:

```angular-html
<Container>
   @if (score() >= 90) {
     <Text text="Grade: A+" color="gold" />
   }
   @else if (score() >= 80) {
     <Text text="Grade: A" color="green" />
   }
   @else if (score() >= 70) {
     <Text text="Grade: B" color="blue" />
   }
   @else if (score() >= 60) {
     <Text text="Grade: C" color="orange" />
   }
   @else {
     <Text text="Grade: F" color="red" />
   }
</Container>

<script>
  const score = signal(85);
</script>
```

### Interactive Example

Try this interactive example to see how `@if/@else if/@else` works:

## Loops

You can use the `@for` directive to loop over an array or an object.

```angular-html
<Container>
  @for (item of items) {
    <Text text={item} />
  }
</Container>

<script>
  const items = ['Hello', 'World'];
</script>
```

`@for` can also be used as the root template structure.

::: warning With objects array

If `item` is a signal (see chapter on reactivity), use `item()` to access it.

```angular-html
<Container>
  @for (item of items) {
    <Text text={item().text} />
  }
</Container>


<script>
  const items = [{
    text: 'Hello'
  }, {
    text: 'World'
  }];
</script>
```

:::

### With objects

You can use the `@for` directive to loop over an object.

```angular-html
<Container>
  @for ((item, key) of items) {
    <Text text={item} /> - <Text text={key} />
  }
</Container>

<script>
  const items = { 'Hello': 'World', 'Foo': 'Bar' }
</script>
```

### Advanced usage

The `@for` directive supports advanced iteration options:

#### Tracking items

When an array is replaced by a new array, use `track` to preserve children whose identity did not change:

```html
<Container>
  @for (item of items; track item.id) {
    <Text text={item.name} />
  }
</Container>

<script>
  const items = signal([{ id: 1, name: 'Player' }]);

  // The child for id 1 is reused.
  items.set([{ id: 1, name: 'Player' }]);
</script>
```

Without `track`, replacing the whole array keeps the legacy behavior and recreates the loop children.

#### Using method calls

You can iterate over the result of a function call:

```html
<Container>
  @for (item of getItems()) {
    <Text text={item} />
  }
</Container>

<script>
  function getItems() {
    return ['Hello', 'World'];
  }
</script>
```

#### Using method calls with parameters

You can pass parameters to the function:

```html
<Container>
  @for (item of getItems(5, 'prefix')) {
    <Text text={item} />
  }
</Container>

<script>
  function getItems(count, prefix) {
    return Array.from({length: count}, (_, i) => `${prefix}-${i+1}`);
  }
</script>
```

#### Using object properties and methods

You can iterate over object properties and methods:

```html
<Container>
  @for (sprite of sprites.items) {
    <Sprite texture={sprite.texture} />
  }
</Container>

<script>
  const sprites = {
    items: [
      { texture: 'player.png' },
      { texture: 'enemy.png' }
    ]
  };
</script>
```

Or combine object properties with method calls:

```html
<Container>
  @for (sprite of gameState.getVisibleSprites(maxCount)) {
    <Sprite texture={sprite.texture} />
  }
</Container>

<script>
  const maxCount = 10;
  const gameState = {
    getVisibleSprites(limit) {
      // Return only visible sprites, limited by count
      return sprites.filter(s => s.visible).slice(0, limit);
    }
  };
</script>
```

The iterable can also use a root function call followed by property access, optional chaining, or a fallback expression:

```html
<Container>
  @for (objective of selectedQuest()?.objectives ?? []; track objective.id) {
    <Text text={objective.label} />
  }
</Container>
```

An iterable containing a function call is compiled as a reactive `computed` source.

## Template errors

Compiler errors include a stable diagnostic code, the source location, the offending line, a pointer, and a suggested correction:

```text
Error parsing template in /app/quest-menu.ce
[CE_TEMPLATE_INVALID_FOR] Invalid @for directive. (line 12, column 3)

12 |   @for (item items) {
   |   ^

Hint: Expected "@for (item of items) { ... }".
```

Dedicated diagnostics cover malformed control directives, invalid dynamic expressions, mismatched or unclosed tags, and unclosed attribute quotes or braces.
