Appearance
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.