smithery/nette

latte-templates

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.

Installation

$ npx skills add smithery/nette --skill latte-templates

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/nette.

npx skills add smithery/nette

Browse all from smithery/nette

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 16,636 B
  • docs SUMMARY.md 135 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Latte Templating System

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

composer require latte/latte

Basic Syntax

{* 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

{$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:

{* 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 |:

{$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:

{$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:

{* 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:

{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

{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):

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

Child template:

{layout '@layout.latte'}

{block title}My Page{/block}

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

Including Templates

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

Blocks

{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'}

Common Tags Reference

Tag Description
{$var} Print escaped variable
{if}...{/if} Condition
{foreach}...{/foreach} Loop
{var $x = ...} Create variable
{include 'file'} Include template
{block name}...{/block} Define block
{layout 'file'} Extend layout
{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 → EUSERWARNING 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).

<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:

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

  1. 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:

<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

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

The standard way is assigning to $this->template:

$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:

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:

/**
 * @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:

{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

{* 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:

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:

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

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

Anti-Patterns to Avoid

  • Don't put business logic in templates – templates display data, they don't process it. Calculations, filtering, and data transformations belong in presenters or services. Complex template logic is a sign that the presenter's render* method isn't preparing data well enough.
  • Don't create deep template hierarchies – more than 2 levels of {layout} inheritance becomes hard to debug. Prefer {include} and {define} for composition over deep inheritance chains.
  • Don't duplicate template code – if the same HTML structure appears in multiple templates, extract it to a {define} block or a partial template (@item.latte). Duplication causes inconsistency when one copy gets updated but not the others.

Online Documentation

For detailed information, use WebFetch on these URLs: