Skip to content
Back to skills

Latte Templates

ASecurity

Invoke before creating or modifying .latte files, even for single-line changes. Provides Latte syntax, tags, filters, n:attributes, layouts, template inheritance, AJAX snippets, extensions, and Nette integration. Also trigger when user mentions Latte by name.

  • 44 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added February 7, 2026
developmentgophpshellexpressapidocumentation

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add nette/claude-code --skill latte-templates --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Latte Templates?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Latte Templates
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nette-latte-templates/badge)](https://www.skillsdirectory.com/skills/nette-latte-templates)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: latte-templates
description: Invoke before creating or modifying .latte files, even for single-line changes. Provides Latte syntax, tags, filters, n:attributes, layouts, template inheritance, AJAX snippets, extensions, and Nette integration. Also trigger when user mentions Latte by name.
---

## Latte Templating System

Latte is a secure templating engine with context-aware escaping, intuitive syntax, and powerful template inheritance.

```shell
composer require latte/latte
```

### Basic Syntax

```latte
{* this is a comment *}
<ul n:if="$items">                {* n:attribute *}
{foreach $items as $item}         {* tag *}
	<li>{$item|capitalize}</li>   {* variable with filter *}
{/foreach}
</ul>
```

### Printing Variables

```latte
{$name}                    {* prints escaped variable *}
{$user->name}              {* object property *}
{$items[0]}                {* array access *}
{='hello'|upper}           {* expression with filter *}
{$html|noescape}           {* disable escaping (use carefully!) *}
```

Latte uses **context-aware escaping** — output adapts to where the variable is printed. In `<script>` context, arrays and objects are automatically serialized to JSON:

```latte
{* Arrays auto-serialize to JSON inside <script> *}
<script type="application/ld+json">{$schemaData}</script>

<script>
	let config = {$config};
</script>
```

Never use `json_encode()` in PHP + `|noescape` for this — Latte handles serialization and escaping safely on its own.

### Filters

Filters modify output, written after `|`:

```latte
{$title|upper}                    {* HELLO *}
{$text|truncate:100}              {* shortens to 100 chars *}
{$price|number:2}                 {* formats number *}
{$date|date:'j. n. Y'}            {* formats date *}
{$name|lower|capitalize}          {* chained filters *}
```

Common filters: `upper`, `lower`, `capitalize`, `truncate`, `number`, `date`, `noescape`, `escapeUrl`, `stripHtml`, `trim`, `replace`, `first`, `last`, `length`, `sort`, `reverse`

The filter `|` applies to the entire expression before it. Parentheses around PHP operators (like `??`) before a filter are unnecessary:

```latte
{$a ?? $b|texyLine|noescape}        {* correct – filter applies to result of ?? *}
{($a ?? $b)|texyLine|noescape}      {* redundant – parentheses change nothing *}
```

See [the complete filter reference](references/filters.md) for all available filters.

### n:attributes

Pair tags can be written as HTML attributes:

```latte
{* These are equivalent: *}
{if $condition}<div>...</div>{/if}
<div n:if="$condition">...</div>

{* Applies to element content only: *}
<div n:inner-foreach="$items as $item">...</div>

{* Applies to tag only (not content): *}
<a href={$url} n:tag-if="$url">Link</a>
```

### Conditions and Loops

`{if}` / `{elseif}` / `{else}`, `{foreach}` and `{for}` work as expected. What is Latte-specific:

```latte
{ifset $user}...{/ifset}          {* the variable exists, unlike {if isset()} *}

{switch $type}                    {* no break, cases do not fall through *}
	{case admin}Administrator
	{case user}User
	{default}Guest
{/switch}

{foreach $items as $item}
	<li>{$item}</li>
{else}                            {* runs when the array is empty *}
	<li>No items found</li>
{/foreach}

{foreach $items as $item}
	{$iterator->counter}. {$item}   {* 1, 2, 3…; also ->first, ->last, ->odd, ->even *}
	{first}<ul>{/first}             {* rendered only in the first iteration *}
	{last}</ul>{/last}
	{sep}, {/sep}                   {* separator, skipped after the last item *}
{/foreach}
```

### Variables

```latte
{var $name = 'John'}
{var $items = [1, 2, 3]}
{default $lang = 'en'}            {* only if not set *}

{capture $content}
	<p>Captured HTML</p>
{/capture}
{$content}
```

### Template Inheritance

**Layout template (`@layout.latte`):**

```latte
<!DOCTYPE html>
<html>
<head>
	<title>{block title}Default{/block}</title>
</head>
<body>
	{block content}{/block}
</body>
</html>
```

**Child template:**

```latte
{layout '@layout.latte'}

{block title}My Page{/block}

{block content}
	<h1>Welcome</h1>
	<p>Content here</p>
{/block}
```

### Including Templates

```latte
{include 'header.latte'}
{include 'item.latte', item: $item, showPrice: true}
{include $dynamicTemplate}
```

### Blocks

```latte
{block sidebar}
	<aside>Sidebar content</aside>
{/block}

{include sidebar}                          {* print block *}
{include sidebar from 'other.latte'}       {* from another file *}

{* Reusable definitions with parameters *}
{define button, $text, $type = 'primary'}
	<button class="btn btn-{$type}">{$text}</button>
{/define}

{include button, 'Submit'}
{include button, 'Cancel', 'secondary'}
```

### Other Common Tags

| Tag | Description |
|-----|-------------|
| `{do expression}` | Execute without output |
| `{php expression}` | Obsolete alias for `{do}` – a single expression only. Real PHP code needs `RawPhpExtension` |
| `{dump $var}` | Debug dump (Tracy) |

See [the complete tag reference](references/tags.md) for all available tags.

### Smart HTML Attributes (Latte 3.1)

Applies only when the **whole** attribute value is one `{...}` expression (`title={$t}` or
`title="{$t}"`). In a mixed value (`title="foo {$t}"`) none of it applies – null becomes `''`,
an array becomes `"Array"`. `n:attr` follows the same rules, so `{if}` / `n:attr` wrappers
around attributes are obsolete.

| Kind | Attributes | Value handling |
|---|---|---|
| bool | `allowfullscreen async autofocus autoplay checked controls default defer disabled formnovalidate inert ismap itemscope loop multiple muted nomodule novalidate open playsinline readonly required reversed selected` | truthy → bare name; falsey (`false`, `null`, `0`, `''`, `[]`) → omitted |
| tristate | `contenteditable draggable spellcheck writingsuggestions` | `bool` → the keywords `"true"` / `"false"`; `null` omits the attribute |
| valued bool | `hidden popover` | flag like a bool, but a non-empty value survives (`hidden="until-found"`); falsy is tested first, so `'0'` omits it rather than rendering an invalid value |
| list | `accesskey class headers itemprop ping rel role sandbox` | array → space-separated |
| style | `style` | array → `prop: value; …` |
| data | `data-*` | array / `stdClass` → JSON; `bool` → `"true"` / `"false"` |
| aria | `aria-*` | `bool` → `"true"` / `"false"`; array → space-separated |
| plain | everything else | `string`/`int`/`float`/`Stringable` printed; `null` omits the attribute; array, `bool` or other object → `E_USER_WARNING` and the attribute is dropped |

In list/aria arrays a plain item is used as-is, a keyed item yields the **key** only if its
value is `=== true` (`active => 1` renders `1`, not `active`); items with `null`, `false`, `0`,
`''` are skipped, an all-skipped array omits the attribute. In `style`, a keyed item is skipped
by the same rule, so `opacity => 0` vanishes (`'0px'` survives).

```latte
<input type="text" disabled={$disabled} readonly={$readonly}>
{* false, true → <input type="text" readonly> *}

<div title={$title}>x</div>          {* null → <div>x</div> *}
<div title="Hi {$name}">x</div>      {* null → <div title="Hi ">x</div> (mixed value) *}
<div title={$title|upper}>x</div>    {* null → <div title="">x</div> (filter stringifies null) *}
<div title={$title?|upper}>x</div>   {* null → <div>x</div> (?| preserves the drop) *}

<button class={[btn, btn-primary, active => $isActive, disabled => $isDisabled]}>x</button>
{* true, false → <button class="btn btn-primary active">x</button> *}

<a rel={[nofollow, external => $isExternal]}>l</a>  {* true → <a rel="nofollow external">l</a> *}
<span title={[a, b]}></span>  {* warning: array is not allowed for 'title'; attribute dropped *}
<input value={$rows}>         {* array → same warning, no more value="Array" *}

<div style={[background => lightblue, opacity => 0, display => $vis ? block : null]}></div>
{* false → <div style="background: lightblue"></div> *}

<div data-config={[theme: dark, version: 2]} data-open={=true}></div>
{* → <div data-config='{"theme":"dark","version":2}' data-open="true"></div> *}

<button aria-expanded={=false} aria-labelledby={[title, desc => $has]}></button>
{* false → <button aria-expanded="false" aria-labelledby="title"></button> *}

<div data-active={$on|toggle}></div>  {* toggle = bool handling for any attribute; false → <div></div> *}
<div title={$cfg|json}></div>         {* json (must be the last filter) = JSON handling *}
```

A real filter defeats smart handling – it stringifies the value: `class={$arr|noescape}`
renders `class="Array"`. Keep smart attributes filter-free; `toggle`, `json`, `nocheck` and
`accept` are compile-time hints, not filters, and are safe.

Traps:

- **URL attributes are exempt** (`href`, `src`, `action`, `formaction`, `<object data>`): Latte
  appends an internal `checkUrl` filter, so `href={$url}` with null renders `href=""`, and an
  array gives `href="Array"` with only a PHP notice. Use `href={$url?|nocheck}` for the drop
  (this also skips the URL safety check).
- Tristate attributes distinguish **three** states: `true` and `false` render the keyword,
  `null` drops the attribute. That matters, because "missing" and `="false"` mean different
  things — `contenteditable="false"` disables editing inside an editable region, whereas a
  missing attribute inherits from the parent.

#### Prefer `class={[...]}` and smart attributes over `n:class` / `n:attr`

Since 3.1 the attribute forms cover almost everything the `n:` ones did, so new templates
should reach for them first. Neither `n:class` nor `n:attr` is deprecated in Latte, and there
is one case only `n:attr` can express (below), so this is a preference, not a removal.

**Do not rewrite existing templates mechanically** — two differences change the rendered
output:

```latte
<a n:class="$cnt ? open, item">      {* → *}  <a class={[$cnt ? open, item]}>  {* keep the ternary *}
<a class={[open => $cnt]}>           {* wrong – $cnt = 3 renders class="3" *}
<a class={[sel => $row]}>            {* wrong – a Stringable renders its string, a plain object throws *}
<option n:attr="selected: $sel">     {* → *}  <option selected={$sel}>
```

1. **A keyed item applies only when its value is `=== true`.** With any other truthy value the
   *value* is printed instead of the key, which is silent. Keep the ternary rather than moving
   the condition into a key.
2. **`n:class` de-duplicates the list** (`array_unique`), `class={[...]}` does not — the same
   class listed twice survives after the rewrite.

`n:class` additionally rejects an array variable (`n:class="$arr"` → `class="Array"`; use
`class={$arr}`) and a sibling `class="…"` on the same tag (CompileException); both fail loudly.

The one case `n:attr` still owns is a **dynamic** set or name of attributes, which no static
attribute can express:

```latte
<tr n:attr="$attrs">          {* ['data-id' => 5, 'class' => 'row'] → data-id="5" class="row" *}
<a n:attr="[$name => $val]">  {* attribute name from a variable *}
```

**Migration from 3.0** – changed: `null` used to print `=""`; `data-`/`aria-` bools printed
`"1"`/`""`; `data-` arrays printed a space-separated list; `on*` values were JSON-encoded.
`$engine->setFeature(Latte\Feature::MigrationWarnings, true)` warns on every affected
attribute; the `|accept` filter silences it per value.

---

## Latte in Nette Applications

### Template Organization Strategy

**Keep templates with presenters:**

```
Product/
├── ProductPresenter.php
├── default.latte
├── edit.latte
└── detail.latte
```

**Layout placement follows presenter organization:**

```
Admin/
├── @layout.latte           ← Admin-wide layout
├── Auth/
│   ├── @layout.latte       ← Auth-specific layout
│   └── AuthPresenter.php
└── Catalog/
	└── Product/
		├── ProductPresenter.php
		└── edit.latte
```

The more specific a layout, the closer it sits to the presenters it serves; whether it extends the enclosing one is up to it. Keep such chains short: a part that only some pages share usually fits better through `{include}` or `{define}` than through another layout level.

### Template Partial Patterns

**Shared template parts use @ prefix:**
- `@layout.latte` - layout templates
- `@form.latte` - reusable form structures
- `@item.latte` - list item templates

### Passing Variables to Templates

Templates display data; computing it (calculations, filtering, aggregation) belongs in `render<View>()` or a service. The standard way is assigning to `$this->template`:

```php
$this->template->article = $this->articles->getById($id);
```

For properties that should always be available in templates, use the `#[TemplateVariable]` attribute (the property **must be public** – on a protected one it throws `LogicException`) instead of repeating assignments in every action:

```php
use Nette\Application\Attributes\TemplateVariable;

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	#[TemplateVariable]
	public string $siteName = 'My blog';
}
```

The property value is automatically passed as `$siteName` in every template. If you explicitly assign `$this->template->siteName` in an action, the explicit value wins.

### Template Class Strategy

**Create template classes for complex presenters:**

```php
/**
 * @property-read ProductTemplate $template
 */
class ProductPresenter extends BasePresenter
{
}

class ProductTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	public ProductRow $product;
	public array $variants;
	public ?CategoryRow $category;
}
```

**When to use template classes:**
- Presenters with 5+ template variables
- Complex data structures passed to templates
- When you want full IDE support in templates

**Template Type Declaration:**

```latte
{templateType App\Presentation\Product\ProductTemplate}

<h1>{$product->name}</h1>
{foreach $variants as $variant}
	<div class="variant">{$variant->name} - {$variant->price}</div>
{/foreach}
```

### Nette-specific Tags

```latte
{* Links *}
<a n:href="Product:detail $id">Detail</a>
<a href={link Product:detail $id}>Detail</a>
<a href={link //Product:detail $id}>Absolute (// makes it absolute)</a>
<a href={plink Product:detail $id}>From a component template, link to a PRESENTER</a>

{* Components *}
{control productForm}
{control dataGrid}

{* AJAX Snippets *}
{snippet items}
	{foreach $items as $item}
		<div>{$item->name}</div>
	{/foreach}
{/snippet}

{* Forms *}
{form loginForm}
	{label username}{input username}
	{label password}{input password}
	{input submit}
{/form}

{* Assets (Nette Assets) *}
{asset 'admin.js'}
{asset 'front.css'}
```

### Extension and Customization

**Create single extension for entire application:**

```php
final class LatteExtension extends Latte\Extension
{
	public function getFilters(): array
	{
		return [
			'money' => fn($amount) => number_format($amount, 0, ',', ' ') . ' Kč',
		];
	}

	public function getFunctions(): array
	{
		return [
			'canEdit' => fn($entity) => $this->user->isAllowed($entity, 'edit'),
		];
	}
}
```

**Register in config:**

```neon
latte:
	strictParsing: yes
	extensions:
		- App\Presentation\Accessory\LatteExtension
```

Enable `strictParsing` to catch template errors early (missing variables, typos in tag names).

### Online Documentation

For details, see the official documentation:

- [Syntax](https://latte.nette.org/en/syntax) – complete syntax guide
- [Tags](https://latte.nette.org/en/tags) – all available tags
- [Filters](https://latte.nette.org/en/filters) – all filters with examples
- [Template Inheritance](https://latte.nette.org/en/template-inheritance) – layouts, blocks, embed
- [Extending Latte](https://latte.nette.org/en/extending-latte) – custom tags, filters, extensions
- [Type System](https://latte.nette.org/en/type-system) – template types and IDE support

Files in this skill

  • SKILL.md8.5 KB
  • filters.md5.2 KB
  • tags.md5.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.