Skip to content

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>

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>[email protected]</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:

Grade System with @if/@else if/@else

Click the buttons to change the score and see different grades

Preview

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.

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.