---
url: /components.md
---
# Components

::: info
Automatically generated from PHP attributes by the `shop:template:documentation` command. CSS must not rely on the generated HTML output of components - it may change in future versions.
:::

## i:gdpr-form

GDPR consent checkbox/text inserted into parent forms

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| codes | string | no |  | GDPR form codes to display (comma-separated) |
| context | cart-sign-up | footer | sidebar | no |  | Override auto-detected context |
| list | boolean | no |  | Render as \<li> elements without wrapper div |

::: info
Self-closing tag, must have no children. Context is auto-detected from the parent form component (newsletter-signup, user-signup, contact form). Use the codes attribute only when you need specific GDPR forms regardless of context.
:::

**Auto-detected context (inside newsletter-signup, user-signup, or contact form)**

```twig
<i:gdpr-form />
```

**Explicit GDPR form codes**

```twig
<i:gdpr-form codes="newsletter_consent,marketing" />
```

Generated output:

```html
<div>gdprForm({"codes":["newsletter_consent","marketing"]})</div>
```

## i:newsletter-signup

Newsletter subscription form

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| redirect | string | no |  | Redirect URL after successful signup |
| autohide | boolean | no |  | Hide form for already subscribed visitors |
| ignore-duplicated | boolean | no |  | Silently ignore duplicate subscriptions |
| confirm-template | string | no |  | Email template ID for confirmation (numeric, requires discount) |
| discount | string | no |  | Discount set ID(s) for coupon code (comma-separated, requires confirm-template) |

| Role | Tag | Required | Description |
|---|---|---|---|
| email | input | yes | Email address input |
| first\_name | input | no | First name |
| last\_name | input | no | Last name |
| street | input | no | Street address |
| city | input | no | City |
| zip | input | no | Postal code |
| country | input | no | Country code (cz, sk, ...) |
| source | input|select | no | Newsletter source (for external system integrations) |
| gender | input | no | Gender radio buttons (value: m or f) |
| submit | button | yes | Submit button |

::: info
When using discount attribute, the confirm-template attribute is also required. The email template can use {{discountCode}} for a single code or discountCodes array keyed by set ID for multiple codes.
:::

**Minimal newsletter form with GDPR consent**

```twig
<i:newsletter-signup>
<input data-role="email" data-label="Your email">
<i:gdpr-form />
<button data-role="submit">Subscribe</button>
</i:newsletter-signup>
```

Generated output:

```html
<form method="POST" action="/w/~Prijemce_Pridat/save/?uri=%2F" data-component="newsletter-signup">
<label for="email_uid">trans:Your email</label><input id="email_uid" name="email" type="email" required="required">
<div>gdprForm({"context":"newsletter-signup"})</div>
<div class="global-newsletter-url"><label for="newsletter_uid">trans:URL</label><input type="url" id="newsletter_uid" name="url"></div>
<button type="submit">trans:Subscribe</button>
</form>
```

**Newsletter form with extra fields and redirect**

```twig
<i:newsletter-signup redirect="/thank-you">
<input data-role="email" data-label="Your email">
<input data-role="first_name" data-label="First name">
<i:gdpr-form />
<button data-role="submit">Subscribe</button>
</i:newsletter-signup>
```

Generated output:

```html
<form method="POST" action="/w/~Prijemce_Pridat/save/?uri=%2F" data-component="newsletter-signup">
<label for="email_uid">trans:Your email</label><input id="email_uid" name="email" type="email" required="required">
<label for="first_name_uid">trans:First name</label><input id="first_name_uid" name="first_name" type="text">
<div>gdprForm({"context":"newsletter-signup"})</div>
<div class="global-newsletter-url"><label for="newsletter_uid">trans:URL</label><input type="url" id="newsletter_uid" name="url"></div>
<button type="submit">trans:Subscribe</button>
<input type="hidden" name="redirect" value="/thank-you"></form>
```

## i:search-form

Product search form with autocomplete support

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| autocomplete | boolean | no |  | Enable search autocomplete |
| min-length | string | no | 3 | Minimum characters before autocomplete triggers |
| template | string | no |  | Template name for search results (alphanumeric/underscore) |
| img-format | string | no |  | Image format for autocomplete results (e.g. "60x60") |

| Role | Tag | Required | Description |
|---|---|---|---|
| search | input | yes | Search text input |
| submit | button | yes | Submit button |

::: info
Autocomplete results template is in `ajax/search.tpl` (or custom file via template attribute). The template receives variables `q` (search query) and `searchPageUrl` (link to the full results listing). Each result link must be annotated with `data-result-type` (product, category, brand, article, page, more) and products with `data-product-id` — either attribute may sit on the link or on a wrapper element around it. Keyboard navigation: mark section wrappers with `data-search-section="N"` (recommended), or number every result with a positive `tabindex` (legacy) — on the link or on its wrapper. Style the `search-current-item` CSS class for the keyboard highlight. Full guide: [Search Autocomplete](/search-autocomplete).
:::

::: info
Fires `autocomplete:loaded` event on the form element after each autocomplete results load.
:::

**Search form with autocomplete**

```twig
<i:search-form autocomplete>
<input data-role="search" data-label="Search">
<button data-role="submit">Search</button>
</i:search-form>
```

Generated output:

```html
<form data-min-length="3" action="Vypis/Vyhledavani" method="GET" data-component="product-search" data-autocomplete>
<label for="search_uid">trans:Search</label><input id="search_uid" type="search" name="fraze" required="required" minlength="2" autocomplete="off">
<button type="submit">trans:Search</button>
</form>
```

## i:comment-form

Comment/review form for products, articles, and categories with proof-of-work spam protection

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| object | expression | yes |  | Variable with the commentable object (product, article, or category) |

| Role | Tag | Required | Description |
|---|---|---|---|
| text | textarea | no | Comment text |
| title | input | no | Comment headline |
| rating | select | no | Star rating 1-5 (products only, options auto-generated) |
| author | input | no | Author name |
| email | input | no | Author email |
| phone | input | no | Author phone |
| parent | input | no | Parent comment ID for threaded replies (auto-set as hidden) |
| submit | button | yes | Submit button |

::: info
Spam protection uses proof-of-work (no CAPTCHA needed). The form submits via AJAX automatically. Rating select options (stars) are auto-populated. For threaded replies, set parent slot value to the parent comment ID.
:::

**Basic product comment**

```twig
<i:comment-form object="product">
<input data-role="author" data-label="Name">
<textarea data-role="text" data-label="Comment"></textarea>
<button data-role="submit">Submit</button>
</i:comment-form>
```

Generated output:

```html
<form method="POST" action="/api/comments" data-component="comment-form">
<label for="author_uid">trans:Name</label><input id="author_uid" name="author" type="text" autocomplete="name">
<label for="text_uid">trans:Comment</label><textarea id="text_uid" name="text"></textarea>
<button type="submit">trans:Submit</button>
<input name="commentType" type="hidden" value="zbozi">
<input name="objectId" type="hidden" value="123">
</form>
```

**Product review with all fields**

```twig
<i:comment-form object="product">
<input data-role="author" data-label="Name">
<input data-role="email" data-label="Email">
<input data-role="title" data-label="Headline">
<textarea data-role="text" data-label="Review"></textarea>
<button data-role="submit">Submit review</button>
</i:comment-form>
```

Generated output:

```html
<form method="POST" action="/api/comments" data-component="comment-form">
<label for="author_uid">trans:Name</label><input id="author_uid" name="author" type="text" autocomplete="name">
<label for="email_uid">trans:Email</label><input id="email_uid" name="email" type="email" autocomplete="email">
<label for="title_uid">trans:Headline</label><input id="title_uid" name="title" type="text">
<label for="text_uid">trans:Review</label><textarea id="text_uid" name="text"></textarea>
<button type="submit">trans:Submit review</button>
<input name="commentType" type="hidden" value="zbozi">
<input name="objectId" type="hidden" value="456">
</form>
```

## i:list-empty

Defines the empty-state template macro shown when listing has no results

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| type | product | article | actuality | yes |  | Listing type |
| name | string | no | default | Empty template macro name |

**Empty state with separate messages for filtered and default states**

```twig
<i:list-item type="product">{{product.name}}</i:list-item><i:list-empty type="product"><p data-role="default">No products found.</p><p data-role="filter">No products match your filter.</p></i:list-empty><i:list-container type="product"><div data-role="empty"></div><div data-role="list"></div></i:list-container>
```

Generated output:

```html
<div data-type="product" data-list-config="eyJjb250ZW50R3JvdXBJZCI6bnVsbCwiY3VycmVudFBhZ2UiOm51bGwsInBlclBhZ2UiOm51bGwsInNvcnQiOm51bGwsImxpc3RUeXBlIjpudWxsLCJsaXN0SWQiOm51bGwsInNlcnZpbmdJZCI6bnVsbCwic2VydmljZSI6bnVsbH0=" data-component="list-container"><div data-role="empty"><p>trans:No products found.</p></div><div data-role="list"></div></div><script>{inline-listing-loaded};</script>
```

## i:list-item

Defines the template macro for a single item in a listing

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| type | product | article | actuality | yes |  | Type of item |
| tag | string | no | div | HTML tag to wrap each item |
| name | string | no | default | Macro name (must match list-container name) |

**Basic product item template**

```twig
<i:list-item type="product"><h3>{{product.name}}</h3></i:list-item>{{_self.item_product_default(product)}}
```

Generated output:

```html
<div data-track="123|0||" data-oid="p123"><h3>name</h3></div>
```

## i:list-container

Main listing container that fetches items from a repository and renders them with pagination

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| type | product | article | actuality | yes |  | Type of items to display |
| source | expression | no |  | Custom repository query parameters as JSON object (e.g. {category: kategorie.id, limit: 8}). recommend\* sources must be wrapped in i:dynamic lazy="auto" and carry limit <= 20 - see docs/tpl/list.md |
| collection | expression | no |  | Custom Twig collection to iterate over instead of repository |
| name | string | no | default | Template macro name for the item box |
| wrap | string | no |  | Wrap items in groups (e.g. "3:div.row" wraps every 3 items in a div.row) |
| append | string | no |  | Insert elements at positions (e.g. "3:div.spacer") |
| insert-before | string | no |  | Insert macro name to call before each item |
| insert-after | string | no |  | Insert macro name to call after each item |
| hide-empty | boolean | no |  | Hide the entire container when no items found (instead of showing empty slot) |
| scroll-offset | string | no |  | Offset for scroll-to behavior |
| analytics-name | string | no |  | Analytics identifier for tracking |

| Role | Tag | Required | Description |
|---|---|---|---|
| list | div | yes | Container for rendered items (must be empty) |
| empty |  | no | Shown when no items found (required unless hide-empty) |
| pagination | div | no | Pagination controls (can have multiple) |

**Default product listing**

```twig
<i:list-item type="product">{{product.name}}</i:list-item><i:list-empty type="product">No products found</i:list-empty><i:list-container type="product"><div data-role="list"></div><div data-role="empty"></div></i:list-container>
```

Generated output:

```html
<div data-type="product" data-list-config="eyJjb250ZW50R3JvdXBJZCI6bnVsbCwiY3VycmVudFBhZ2UiOm51bGwsInBlclBhZ2UiOm51bGwsInNvcnQiOm51bGwsImxpc3RUeXBlIjpudWxsLCJsaXN0SWQiOm51bGwsInNlcnZpbmdJZCI6bnVsbCwic2VydmljZSI6bnVsbH0=" data-component="list-container"><div data-role="list"><div data-track="123|0|1|" data-oid="p123">abc</div></div><div data-role="empty"></div></div><script>{inline-listing-loaded};</script>
```

**Custom product listing with category filter**

```twig
<i:list-container type="product" source="{category: kategorie.id, limit: 4}">
<div data-role="list"></div>
<div data-role="empty">No products</div>
</i:list-container>
```

## i:list-pagination

Defines the pagination template macro for listings

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| type | product | article | actuality | yes |  | Listing type |
| name | string | no | default | Pagination macro name |

| Role | Tag | Required | Description |
|---|---|---|---|
| add-page | a | no | Load-more button, shown when next page exists |

**Simple load-more pagination**

```twig
<i:list-pagination type="product">
<a data-role="add-page">Load more</a>
</i:list-pagination>
```

## i:list-dynamic

Dynamic content area within a listing that renders content based on type (headline, text, subcategories)

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| type | headline | text | subcategories | other | yes |  | Type of dynamic content |
| variables | string | yes |  | Twig variables to pass to the dynamic content |

**Category headline in listing**

```twig
<div i:list-dynamic type="headline" variables="kategorie">{{ kategorie.name }}</div>
```

## i:banners

Banner/slider component with responsive images, CTA buttons, and carousel support

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| source | expression | yes |  | Twig expression returning banner collection |
| class | string | no |  | CSS class for the banner wrapper |
| plugin | string | no |  | Slider plugin name (e.g. "swiper") |
| click | cta | banner | off | auto | no | auto | Click behavior - CTA button, whole banner, disabled, or auto |
| visible-slides | string | no | 1 | Number of visible slides |
| priority | string | no | high | Loading priority (high = no lazy load for visible slides) |
| wrap | string | no |  | Wrapper element (e.g. "div.banner-inner") |
| arrows | boolean | no |  | Show navigation arrows |
| dots | boolean | no |  | Show pagination dots |
| thumbnails | boolean | no |  | Show thumbnail navigation |
| progress | boolean | no |  | Show progress bar |
| transparent | boolean | no |  | Preserve image transparency |
| fill | boolean | no |  | Use object-fit: cover on images |
| quality | low | normal | high | lossless | no |  | Image compression quality (defaults to the shop setting; use "high" for large hero banners to avoid visible recompression softening) |

**Responsive hero banner with Swiper plugin**

```twig
<i:banners source="banners" class="hero-banner" plugin="swiper" arrows dots><source format="1200x400" src="desktop"/><source format="400x300" src="mobile"/></i:banners>
```

Generated output:

```html
<div class="banner hero-banner swiper-container">
<div class="banner-wrap swiper-wrapper">
<div class="banner-item banner--item__1 banner--item__first banner--item__last swiper-slide">
<picture>
<source srcset="https://i00.eu/img/999999/400x300/b70ltmq7/17.webp 1x,https://i00.eu/img/999999/800x600c/14pro84s/17.webp 2x" media="(max-width: 400px)">
<img width="730" height="400" srcset="https://i00.eu/img/999999/1200x400/eb9g3pmd/17.webp 1x,https://i00.eu/img/999999/2400x800c/aergzgi6/17.webp 2x" src="https://i00.eu/img/999999/1200x400/eb9g3pmd/17.webp">
</picture>
</div>
</div>
<div class="pagination swiper-pagination"></div>
<div class="button-next swiper-button-next"></div>
<div class="button-prev swiper-button-prev"></div>
</div>
```

## i:menu

Defines a menu macro in \_menu.tpl, rendered by i:menu-container

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | yes |  | Menu macro name (must match i:menu-container name) |

::: info
Must be defined in \_menu.tpl file. The `lazy` variable is automatically passed by i:menu-container - use it to skip rendering deep submenus that will be loaded via AJAX. Menu output is cached, so use `active_menu_placeholder_class('className', category)` for active state instead of Twig conditionals. Also accepts a route hash like `active_menu_placeholder_class('active', 'Vypis/Slevy')` for page type matching.
:::

**Two-level category menu with active class and lazy support**

```twig
<i:menu name="main">
<ul>
{% for category in repository.category.findBy({'parent': 0}) %}
<li class="{{ active_menu_placeholder_class('active', category) }}">
<a href="{{ category.url }}">{{ category.name }}</a>
{% if not lazy and category.subcategories is not empty %}
<ul>
{% for sub in category.subcategories %}
<li class="{{ active_menu_placeholder_class('active', sub) }}">
<a href="{{ sub.url }}">{{ sub.name }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</i:menu>
```

## i:menu-container

Category menu container that loads menu from \_menu.tpl template with optional lazy loading

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | yes |  | Menu macro name defined in \_menu.tpl |
| tag | string | no | div | HTML wrapper tag |
| lazy | boolean | no |  | Load menu asynchronously via AJAX |

::: info
Must be an empty element (no children). With `lazy`, the non-lazy part renders server-side and the full menu loads via AJAX, significantly improving page speed. Fires `menu:load` event on the container element after async load completes.
:::

**Lazy-loaded main navigation menu**

```twig
<div><i:menu-container name="main" tag="div" lazy /></div>
```

Generated output:

```html
<div><div id="js-menu-uid">trans:hello</div><script>(function(){var x=new XMLHttpRequest();x.open("GET","/ajax-menu-url");x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.send();x.onreadystatechange=function(){if(4===x.readyState){var t=x.responseText;var l=JSON.parse(document.body.dataset["activeCategories"]),r=document.body.dataset["routeHash"];for(var i=0;i<l.length;i++)t=t.replace(new RegExp("([a-zA-Z0-9\-_]+)\-menu\-class\-placeholder\-"+l[i]+"([^0-9])","g"),"$1$2");t=t.replace(new RegExp("([a-zA-Z0-9\-_]+)\-menu\-class\-placeholder\-"+r+"([^0-9a-z])","g"),"$1$2");document.getElementById("js-menu-uid").innerHTML=t.replace(new RegExp("[a-zA-Z0-9\-_]+\-menu\-class\-placeholder\-[0-9]+","g"),"");const event = document.createEvent("CustomEvent");event.initCustomEvent("menu:load", true, true, {});document.getElementById("js-menu-uid").dispatchEvent(event);}};})()</script></div>
```

**Sidebar menu rendered as list**

```twig
<div i:menu-container name="sidebar" tag="ul"></div>
```

## i:delivery-transports

Container for transport/shipping options in the checkout process

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| tag | string | no | div | HTML wrapper tag |
| reload-targets | string | no |  | CSS selectors of elements to reload when transport changes |

**Transport selection in checkout**

```twig
<i:delivery-transports>{% for transport in cart.availableTransportMethods %}<i:delivery-transport transport="transport" tag="div"><input name="transport" type="radio" />{{ transport.name }}<i:delivery-branch button-text="Choose branch" /></i:delivery-transport>{% endfor %}</i:delivery-transports>
```

Generated output:

```html
<div data-component="delivery-transports" data-dynamic-content="7eNRw6uOGJUoTV9lG4yoJ7tzbjZVbB5agwTz0Ph+6ks="><div data-component="delivery-transport"><input name="transport" type="radio" value="7">PPL<div data-component="delivery-branch"><input type="hidden" name="pobocka[zasilkovna_stat_1000]" value="5" data-config='{"type":"ppl"}' data-branch='{"code":5,"label":"Drtinova 10"}' data-excluded-places="[]"><span>Drtinova 10</span> <a href="#">trans:Choose branch</a></div></div></div>
```

## i:delivery-transport

Single transport option within delivery-transports

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| transport | expression | yes |  | Twig expression for the transport object from cart.transports loop |
| tag | string | yes |  | HTML wrapper tag |

**Transport option with branch selection**

```twig
<i:delivery-transport transport="transport" tag="label"><input name="transport" type="radio" />{{ transport.name }}<i:delivery-branch button-text="Choose pickup point" /></i:delivery-transport>
```

Generated output:

```html
<label data-component="delivery-transport"><input name="transport" type="radio" value="3">PPL<div data-component="delivery-branch"><input type="hidden" name="pobocka[ppl_cz]" data-config='{"type":"ppl"}' data-excluded-places="[]"><span></span> <a href="#">trans:Choose pickup point</a></div></label>
```

## i:delivery-payments

Container for payment method options in the checkout process

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| tag | string | no | div | HTML wrapper tag |
| reload-targets | string | no |  | CSS selectors of elements to reload when payment changes |

**Payment method selection in checkout**

```twig
<i:delivery-payments>{% for payment in cart.availablePaymentMethods %}<i:delivery-payment payment="payment" tag="label"><input name="payment" type="radio" />{{ payment.name }}</i:delivery-payment>{% endfor %}</i:delivery-payments>
```

Generated output:

```html
<div data-component="delivery-payments" data-dynamic-content="7eNRw6uOGJUoTV9lG4yoJ7tzbjZVbB5agwTz0Ph+6ks="><label data-component="delivery-payment"><input name="payment" type="radio" value="7">Credit card</label></div>
```

## i:delivery-payment

Single payment method option within delivery-payments

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| payment | expression | yes |  | Twig expression for the payment object from cart.payments loop |
| tag | string | yes |  | HTML wrapper tag |

**Payment option with radio button**

```twig
<i:delivery-payment payment="payment" tag="label"><input name="payment" type="radio" />{{ payment.name }}</i:delivery-payment>
```

Generated output:

```html
<label data-component="delivery-payment"><input name="payment" type="radio" value="5">Credit card</label>
```

## i:delivery-branch

Pickup point/branch selector within a delivery-transport (renders branch select widget)

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| transport | expression | yes |  | Transport object reference (auto-set by parent delivery-transport) |
| button-text | string | yes |  | Text for the branch selection button (translatable) |

**Branch selector (must be inside i:delivery-transport)**

```twig
<i:delivery-branch button-text="Choose pickup point" />
```

## i:filter-form

Defines a filter form template macro for product/article filtering

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | yes |  | Filter form macro name (referenced by filter-placeholder) |
| auto-scroll | string | no |  | Scroll to listing after filter change |

| Role | Tag | Required | Description |
|---|---|---|---|
| reset | a | no | Filter reset link/button (hidden when no filter is active) |

**Basic filter form with reset button**

```twig
<i:filter-form name="default">
<a data-role="reset">Reset filters</a>
{% for attr in attributes %}
<i:filter-attribute attribute="attr" />
{% endfor %}
</i:filter-form>
```

## i:filter-placeholder

Renders a filter-form macro at a specific location with given configuration

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | yes |  | Filter form macro name (must match a filter-form name) |
| config | string | yes |  | Filter configuration code |
| hide-empty | boolean | no |  | Hide the filter form when no filter attributes are available |
| always-collapsed | boolean | no |  | Render all filter groups initially collapsed |

**Render default filter form, hidden when empty**

```twig
<i:filter-placeholder name="default" config="default" hide-empty />
```

## i:variant-choice

Product variant selector with step-by-step attribute selection

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| data-product | expression | yes |  | Product ID expression |
| reload-targets | string | no |  | CSS selectors of elements to reload when variant changes |

| Role | Tag | Required | Description |
|---|---|---|---|
| step |  | yes | Container for each variant selection step |

**Variant selector with color/size steps**

```twig
<div i:variant-choice data-product="{{product.id}}" i:dynamic="product" reload-targets=".product-price,.product-availability">
{% for step in product.variantChoice.steps %}
<div data-role="step">
<strong>{{ step.name }}:</strong>
{% for value in step.values %}
<button>{{ value.name }}</button>
{% endfor %}
</div>
{% endfor %}
</div>
```

## i:dynamic

Wraps content for dynamic (AJAX) reloading when server-side state changes

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| variables | string | yes |  | Comma-separated Twig variables that trigger re-render |
| lazy | none | auto | manual | no | none | Lazy loading mode - none (server-rendered), auto (loads near the viewport), manual. Required (auto/manual) for list-containers with recommend\* sources - see docs/tpl/list.md |

**Cart summary that updates dynamically**

```twig
<i:dynamic variables="order">Order #{{ order.id }}</i:dynamic>
```

Generated output:

```html
<div data-dynamic-content="+U8r+mkvbR7EJVHWtAmbvIN9KvgnUinu7XHN9oUfE9Nb6OKzPQzr8vs6btDlfyPu1PqQ3yj1hmwFpfxEwO5Lxg==">Order #7</div>
```

## i:img

Responsive image with automatic srcset, DPR-aware rendering, and lazy loading

| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
| src | expression | yes |  | Twig expression returning an Image object (e.g. product.image) |
| format | string | yes |  | Dimensions WxH or comma-separated list (e.g. "359x479" or "800x600,400x300") |
| sizes | string | no |  | HTML sizes attribute (required when multiple formats) |
| fill | boolean | no |  | Use object-fit: cover |
| vector | boolean | no |  | SVG source - skip bitmap optimization |
| transparent | boolean | no |  | Preserve transparency (PNG/WebP instead of JPEG) |
| watermark | boolean | no |  | Apply watermark overlay |
| quality | low | normal | high | lossless | no |  | Compression quality |
| loading | string | no |  | Loading strategy (lazy/eager) |
| lazyload | boolean | no |  | Enable lazy loading |

**Basic product image**

```twig
<i:img src="product.img" format="100x100" fill />
```

Generated output:

```html
<img alt="description" width="100" height="100" srcset="http://100x100f.jpg 1x,http://200x200fc.jpg 2x" src="http://100x100f.jpg">
```

**Lazy-loaded lifestyle image variant**

```twig
<i:img src="product.image('lifestyle')" format="800x600" fill loading="lazy" />
```

**SVG brand logo**

```twig
<i:img src="brand.image" format="200x100" vector transparent fill />
```
