Skip to main content

Template Syntax

LUAT uses a Svelte-inspired syntax for expressing dynamic content, control flow, and component composition.

Expressions

Text Interpolation

Use curly braces to output dynamic content. Values are automatically HTML-escaped for security:

<h1>{props.title}</h1>
<p>Welcome, {props.user.name}!</p>
<span>Price: ${props.price}</span>
main.luat
Loading Luat...

Raw HTML

Use {@html} to output unescaped HTML content:

main.luat
Loading Luat...

⚠️ Warning: Only use {@html} with trusted content to avoid XSS vulnerabilities.

Attributes

Dynamic attributes work the same as text interpolation:

main.luat
Loading Luat...

Attribute Shorthand

When an attribute name is the same as the variable holding its value, you can use a shorthand:

main.luat
Loading Luat...

For more advanced techniques, including conditional classes and integrating with client-side libraries, see the Dynamic Attributes & Classes guide.

Control Flow

If Blocks

Conditional rendering with {#if}, {:else}, and {/if}:

{#if props.user.isAdmin}
<div class="admin-panel">
<h2>Admin Dashboard</h2>
</div>
{:else}
<div class="user-panel">
<h2>User Dashboard</h2>
</div>
{/if}
main.luat
Loading Luat...

Multiple conditions with {:else if}:

{#if props.status === "loading"}
<div class="spinner">Loading...</div>
{:else if props.status === "error"}
<div class="error">Something went wrong</div>
{:else}
<div class="content">{props.data}</div>
{/if}

Each Blocks

Iterate over arrays with {#each}, {:empty}, and {/each}:

<ul>
{#each props.items as item}
<li>{item.name}</li>
{/each}
</ul>
main.luat
Loading Luat...

With index:

<ol>
{#each props.items as item, index}
<li>{index + 1}. {item.name}</li>
{/each}
</ol>

With empty state:

<div class="item-list">
{#each props.items as item}
<div class="item">{item.name}</div>
{:empty}
<div class="no-items">No items found</div>
{/each}
</div>

Props Spread Operator

The spread operator (...) allows you to pass all properties from a table to a component. This is useful when you want to pass multiple properties at once without listing each one individually.

<script>
local Button = require("components/Button")
local buttonProps = { label = "Save", type = "button" }
</script>

<Button {...buttonProps} />

In this example, all key-value pairs from buttonProps will be passed to the Button component.

Loading Luat...

Combining Multiple Spreads

You can use multiple spread operators in a single component:

<script>
local Card = require("components/Card")
local baseProps = { title = "My Card", size = "medium" }
local themeProps = { variant = "primary", outlined = true }
</script>

<Card {...baseProps} {...themeProps} />
Loading Luat...

Combining Spreads with Regular Props

Spread operators can be used together with regular prop assignments. Properties defined directly will override those from spread objects:

<script>
local Button = require("components/Button")
local baseProps = { label = "Default", type = "button" }
</script>

<Button {...baseProps} label="Submit" />

In this case, the button will render with label="Submit" (from the direct prop) and type="button" (from the spread).

Precedence Rules

When multiple properties with the same name are specified, the precedence is as follows:

  1. Direct props (highest precedence)
  2. Spread operators, with later spreads overriding earlier ones
<script>
local baseProps = { type = "button", size = "medium" }
local overrideProps = { size = "large" }
</script>

<!-- Results in type="reset" (direct prop), size="large" (from overrideProps) -->
<Button {...baseProps} type="reset" {...overrideProps} />

Declaring Local Variables

The {@local} tag allows you to declare local variables directly within a block's scope. This is useful for creating derived values that you want to reference multiple times, making your templates cleaner and more efficient without needing a separate <script> block.

The variable is only available inside the block where it is defined.

main.luat
Loading Luat...

Usage Constraints

The {@local} tag must be an immediate child of a block or component. It cannot be used at the top level of a template.

<!-- Invalid: {@local} cannot be a top-level tag -->
{@local myVar = 'this will cause an error'}

Sensitive Content

For logging and debugging purposes, mark sensitive content blocks that should be tracked:

Sensitive If Blocks

{!if props.showSecret}
<!-- This block will be logged as sensitive -->
<div class="secret">
<p>API Key: {props.apiKey}</p>
</div>
{/if}

Sensitive Each Blocks

{!each props.secrets as secret}
<div class="secret-item">{secret.value}</div>
{/each}

These blocks function identically to regular control flow but are marked for special handling in logs and debugging tools.

Comments

HTML Comments

Standard HTML comments are preserved in the output:

<!-- This comment appears in the rendered HTML -->
<div>Content</div>

Template Comments

LUAT comments are removed during compilation:

{/* This comment is only visible in the source */}
<div>Content</div>

Whitespace Handling

LUAT preserves whitespace in your templates, but you can control it:

Compact Syntax

<span>{props.firstName}</span> <span>{props.lastName}</span>

Multi-line with Preserved Formatting

<div class="poem">
{props.line1}
{props.line2}
{props.line3}
</div>

Advanced Expressions

Lua Expressions

Any valid Lua expression can be used within braces:

main.luat
Loading Luat...

Object Access

Access nested properties with dot notation:

<div>
<h2>{props.user.profile.displayName}</h2>
<img src={props.user.profile.avatar.url}>
<p>{props.user.settings.theme}</p>
</div>
main.luat
Loading Luat...

Array Access

<script>
local items = props.items
local itemCount = #items
</script>

<div>
<h3>First item: {items[1].name}</h3>
<p>Item count: {itemCount}</p>
</div>
main.luat
Loading Luat...

Special Characters

Escape special characters when needed:

<!-- Literal braces -->
<pre>Use \{curly braces\} for expressions</pre>

<!-- Quotes in attributes -->
<button onclick="alert('Hello {props.name}')">Click me</button>

Best Practices

1. Keep expressions simple

<!-- Good -->
<span>{props.price}</span>

<!-- Better extracted to script -->
<script>
local formattedPrice = formatCurrency(props.price)
</script>
<span>{formattedPrice}</span>

2. Use meaningful variable names

<script>
local userName = props.user and props.user.name or "Guest"
local isLoggedIn = props.user ~= nil
</script>

<div class="header">
{#if isLoggedIn}
<span>Welcome, {userName}!</span>
{/if}
</div>

3. Handle nil/undefined gracefully

<!-- Safe property access -->
<img src={props.user and props.user.avatar or "/default-avatar.png"}>

<!-- With helper functions -->
<script>
local avatarUrl = getAvatarUrl(props.user)
</script>
<img src={avatarUrl}>