--- url: /api.md --- # API ## Customer Addresses ```html
``` ```javascript document.querySelector('form.address').addEventListener('submit', function(event){ event.preventDefault(); var button = this.querySelector('[type="submit"]'); button.disabled = true; button.classList.add('is-loading'); api.user.address.add(new FormData(this)) .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.address-error').textContent = error.message; }) .finally(function(){ button.disabled = false; button.classList.remove('is-loading'); }); }); document.querySelector('form.address').addEventListener('submit', function(event){ event.preventDefault(); var button = this.querySelector('[type="submit"]'); button.disabled = true; button.classList.add('is-loading'); api.user.address.update(123456789, new FormData(this)) .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.address-error').textContent = error.message; }) .finally(function(){ button.disabled = false; button.classList.remove('is-loading'); }); }); document.querySelector('a.delete-address').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.user.address.delete(123456789) .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.address-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); api.user.address.get(123456789) .then(function(response){ document.querySelector('[name="firstName"]').value = response.firstName; document.querySelector('[name="lastName"]').value = response.lastName; // ... fill remaining fields }) .catch(function(error){ document.querySelector('.address-error').textContent = error.message; }); ``` ## Newsletter (logged-in customer) ### api.user.newsletter.unsubscribe ```javascript document.querySelector('a.customer-unsubscribe').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.user.newsletter.unsubscribe() .then(function(){ window.location.reload(); }) .catch(function(error){ document.querySelector('.newsletter-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ## Order ### api.order.delete ```javascript document.querySelector('a.delete-order').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.order.delete() .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.order-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.order.voucher.add ```javascript document.querySelector('a.add-voucher').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.order.voucher.add('XYZ1234') .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.voucher-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.order.voucher.delete ```javascript document.querySelector('a.delete-voucher').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.order.voucher.delete('XYZ1234') .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.voucher-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.order.item.changeAmount ```javascript document.querySelector('a.remove-change-amount').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.order.item.changeAmount(/* item id*/ 123, 7 /* new amount (cannot be zero) */) .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.cart-error').textContent = error.message; // Currently one of stock, unavailable, min_amount, max_amount, amount_multiple // More can be added later - always fallback to error message console.log(error.code); // translated error message console.log(error.message); // item name (useful if rule was triggered by bundle part) console.log(error.item.name); // correct amount for current validation (may not be valid for next validation) // zero for unavailable items console.log(error.recommendedAmount); }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.order.item.remove ```javascript document.querySelector('a.remove-cart-item').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.order.item.delete(123) .then(function(response){ window.location.reload(); }) .catch(function(error){ document.querySelector('.cart-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.order.status.lookup Look up order status using the order code and email. This method is protected by proof-of-work and rate limiting. ```javascript document.querySelector('form.order-lookup').addEventListener('submit', function(event){ event.preventDefault(); var button = this.querySelector('[type="submit"]'); button.disabled = true; button.classList.add('is-loading'); var orderCode = this.querySelector('[name="orderCode"]').value; var email = this.querySelector('[name="email"]').value; api.order.status.lookup(orderCode, email) .then(function(response){ // redirect to order status page window.location.href = response.url; }) .catch(function(error){ document.querySelector('.order-lookup-error').textContent = error.message; }) .finally(function(){ button.disabled = false; button.classList.remove('is-loading'); }); }); ``` ## Products ### api.product.compare.add ```javascript document.querySelector('a.compare-product').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.product.compare.add(123) .then(function(response){ response.count // number of products in comparison response.url // URL to the product comparison page }) .catch(function(error){ document.querySelector('.compare-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.product.compare.remove ```javascript document.querySelector('a.compare-product').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.product.compare.remove(123) .then(function(response){ response.count // number of products in comparison response.url // URL to the product comparison page }) .catch(function(error){ document.querySelector('.compare-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.product.compare.clear ```javascript document.querySelector('a.compare-product').addEventListener('click', function(event){ event.preventDefault(); var link = this; link.classList.add('is-loading'); api.product.compare.remove() .then(function(response){ }) .catch(function(error){ document.querySelector('.compare-error').textContent = error.message; }) .finally(function(){ link.classList.remove('is-loading'); }); }); ``` ### api.log.feature.used Feature usage logging is intended for tracking less frequently used features over a limited period -- for example, for A/B testing. The name can be arbitrary -- it is only used for debug reporting. ```javascript document.querySelector('a.compare-product').addEventListener('click', function(event){ event.preventDefault(); api.log.feature.used('product-added-to-compare'); }); ``` ### api.geo.suggest.location Returns a list of locations (addresses, cities, regions, ...) for the given phrase. Optionally, results can be filtered by country ("cz") or biased by coordinates. If the exact location is not known, it should always be used at least with GeoIP coordinates. ```javascript api.geo.suggest.location('Drtinova 10', 'cz', 50.0843, 14.4075); ``` --- --- url: /cart.md --- # Checkout Process ## Checkout Flow Architecture The checkout is a multi-step process controlled by the `sekce` (section) variable. The main template `kosik.tpl` acts as a router, including the appropriate step partial based on the current section: ```twig {# kosik.tpl — checkout router #} {% if kos %} {# nesouhlas = terms and conditions not accepted #} {% if nesouhlas %}
{% trans 'checkout.terms-and-conditions.error.message' %}
{% endif %} {% if not sekce or sekce == 'kosik' %} {% include "kosik_obsah.tpl" %} {# cart content step #} {% elseif sekce == 'udaje' %} {% include "kosik_udaje.tpl" %} {# address entry step #} {% elseif sekce == 'dodani' %} {% include "kosik_dodani.tpl" %} {# delivery selection step #} {% endif %} {% else %} {% include 'kosik_prazdny.tpl' %} {# empty cart #} {% endif %} ``` ### Step Templates | Template | Purpose | |---|---| | `kosik.tpl` | Router — includes step partials based on `sekce` | | `kosik_obsah.tpl` | Cart content — product list, quantities, discount codes | | `kosik_dodani.tpl` | Delivery — transport and payment selection (see [Delivery](/delivery)) | | `kosik_udaje.tpl` | Address — customer details entry | | `kosik_souhrn.tpl` | Order summary sidebar — included in every step | | `kosik_prazdny.tpl` | Empty cart fallback — shown when `kos` is falsy | | `kosik_kroky.tpl` | Step indicator breadcrumb — included within each step | | `kosik_darky.tpl` | Gift selection — included when `this.slaveConfig.enableGifts` is true | The step flow is configured in `config.json` via the `cartSteps` setting (see [Configuration](/config)), which maps the progression: content -> delivery -> address. Each step form contains a hidden `toSekce` field that tells the server which step to advance to on submit. ### Cross-sell Products After the checkout steps, `kosik.tpl` can display a cross-sell product carousel using a [content grid](/content-grid) slot: ```twig {# Show cross-sell products below checkout when on the cart content step #} {% if kos and not sekce and this.contentGrid('checkout-select-products').section.product is not empty %} {% set section = this.contentGrid('checkout-select-products').section %}

{{ section.headline }}

{% endif %} ``` ## Cart Items The cart content step (`kosik_obsah.tpl`) displays products using the `cart.catalogItems` loop. The entire product list is wrapped in a form that posts to `this.urlSave`. The cart content area uses `i:dynamic="cart, objednavka"` so it reloads automatically when cart state changes (e.g., quantity updates). See [Syntax - Dynamic Content Reload](/syntax#dynamic-content-reload) for more on `i:dynamic`. ### Info Banner An optional informational banner can be displayed above the cart items using a content grid slot: ```twig {% if this.contentGrid('checkout-content-info') %} {% set block = this.contentGrid('checkout-content-info').checkouttext %}
{{ block.text }}
{% endif %} ``` ### Product List ```twig

{% trans 'checkout.headline' %} ({{ cart.catalogItemTotalAmount }} {% trans 'template.qty' %})

{% for orderItem in cart.catalogItems %}
{# Product image — use variant image if available #} {% if orderItem.variant.image %} {% else %} {% endif %} {# Product name and child items (e.g., bundle contents) #} {{ orderItem.item.name }} {% if orderItem.children is not empty %} {% for item in orderItem.children %} {{ item.name }} {{ item.variantName }} {% if item.priceTotal.current.value != 0 %} ({{ item.priceTotal.current.formatted }}) {% endif %} {% endfor %} {% endif %} {# Variant name #} {% if orderItem.variantName %} {% trans 'template.variant' %}: {{ orderItem.variantName }} {% endif %} {# Unit price with optional discount display #} {% if orderItem.item.price.discount %} {{ orderItem.item.price.beforeDiscount.formatted }} {% endif %} {{ orderItem.item.price.current.formatted }} {# Quantity input — name uses ks[orderItem.id] pattern #} {# For gift items, show "1 qty" text instead of input #} {% if orderItem.gift %} 1 {% trans 'template.qty' %} {% else %} {% endif %} {# Total price per item #} {% if orderItem.gift %} {% trans 'global.free' %} {% else %} {{ orderItem.priceTotal.current.formatted }} {% endif %} {# Remove link — uses ?del= query parameter #} {% if not orderItem.gift %} {% trans 'cart-popup.item.delete' %} {% endif %}
{% endfor %} {# Hidden field to advance to delivery step on submit #}
``` ### orderItem Properties | Property | Description | |---|---| | `orderItem.product.url` | URL to the product detail page | | `orderItem.image` | Product image | | `orderItem.variant.image` | Variant-specific image (if available) | | `orderItem.item.name` | Product name | | `orderItem.variantName` | Selected variant name | | `orderItem.item.price.current.formatted` | Formatted unit price | | `orderItem.item.price.beforeDiscount.formatted` | Price before discount | | `orderItem.item.price.discountPercent` | Discount percentage | | `orderItem.priceTotal.current.formatted` | Total price for the line | | `orderItem.amount` | Quantity in cart | | `orderItem.id` | Order item ID (used for quantity input names and removal) | | `orderItem.gift` | Whether the item is a gift (read-only quantity, free) | | `orderItem.children` | Child items for bundles/sets | | `orderItem.flagNew` | Whether the product is flagged as new | | `orderItem.labels` | Custom product labels | | `orderItem.freeDelivery` | Whether the item qualifies for free delivery | | `orderItem.item.availability.text` | Availability text | | `orderItem.item.availability.hours` | Availability in hours | | `orderItem.availableUpsells` | Per-item upsell products | ### Per-Item Upsells Each cart item can offer upsell products (e.g., extended warranty, gift wrapping). These are rendered as checkboxes within the item row: ```twig {% if orderItem.availableUpsells is not empty and not orderItem.gift %} {% trans 'cart-dialog.upsells.headline' %} {% for upsell in orderItem.availableUpsells %} {% endfor %} {% endif %} ``` ### Loyalty Program Integration When the loyalty program is enabled, customers can apply loyalty points to their order: ```twig {% if customer and this.slaveConfig.enableLoyaltyProgram and cart.useLoyaltyPointsManually %} {% trans 'checkout.points.text' %} {{ customer.userPointsAvailable }} {% trans %}user.credits-count{% plural customer.userPointsAvailable %}user.credits-count-plural{% endtrans %}: {# Hidden input ensures value submits even when checkbox is unchecked #} {% endif %} ``` The cart page also shows how many points the order will earn, and prompts unregistered users to sign up: ```twig {% if this.slaveConfig.enableLoyaltyProgram %} {% trans 'checkout.loyalty.you-get' %} {{ cart.loyaltyPoints }} {% trans %}user.credits-count{% plural cart.loyaltyPoints %}user.credits-count-plural{% endtrans %} {% trans 'checkout.loyalty.discount-to-next-purchase' %}. {% if not user %} {% trans 'checkout.loyalty.dont-forget' %} {% trans 'checkout.loyalty.register' %}. {% endif %} {% endif %} ``` ## Discount Codes Discount items applied to the cart are displayed using the `cart.discountItems` loop. They appear after the product list in `kosik_obsah.tpl`: ```twig {% for orderDiscountCart in cart.discountItems %}
{# Discount icon from SVG sprite #} {# Discount name #} {{ orderDiscountCart.name }} {# Discount value — either percentage or fixed amount #} {% if orderDiscountCart.percent > 0 %} -{{ orderDiscountCart.percent }}% {% elseif orderDiscountCart.price.current.value > 0 %} {{ orderDiscountCart.price.current.formatted }} {% endif %} {# Remove discount link — uses ?delSleva= query parameter #} {% trans 'cart-popup.item.delete' %}
{% endfor %} ``` The voucher code input is typically placed in the order summary sidebar (`kosik_souhrn.tpl`), sending the code via the `slev_kod` form field. ## Gift Selection When gift selection is enabled in the shop configuration, the cart content step includes a gift picker partial: ```twig {% if this.slaveConfig.enableGifts %} {% include 'kosik_darky.tpl' %} {% endif %} ``` ## Order Summary Sidebar Each checkout step includes the order summary sidebar by passing a `checkout` context variable that controls what the sidebar displays (totals, buttons, voucher input): ```twig {# In kosik_obsah.tpl (cart content step) #} {% include 'kosik_souhrn.tpl' with {'checkout': 'products'} %} {# In kosik_dodani.tpl (delivery step) #} {% include 'kosik_souhrn.tpl' with {'checkout': 'delivery'} %} ``` The sidebar adapts its content based on the `checkout` variable — showing different submit buttons and information depending on the current step. ## Variant Selection Enabling variant selection in the cart detail requires three steps: 1. In config.json, list the supported selection modes (dropdown, icons, ...) 2. In the shop admin panel, define the steps (which attribute set uses which selection mode, labels, ...) 3. In the template, implement `i:variant-choice` ### i:variant-choice The i:variant-choice tag is used inside the add-to-cart form. It handles rendering the required number of steps, progressive loading of steps, pre-selection based on URL or customer preferences, URL updates on selection, etc. **Important elements:** * *else* branch - if multiple selection modes are supported, one (ideally the simplest, such as a dropdown) should be in the *else* branch. If the template only accounts for certain modes and the shop expects different ones, purchasing would be impossible -- the final *else* branch ensures the purchase can still proceed. * `data-role="step"` - wrapper defining individual steps; when loading choices based on the current selection, only the subsequent steps are replaced * `reload-targets` - classes (or CSS selector) of elements that should be updated on each selection change -- typically images, price, availability, etc. These elements must first be marked with *i:dynamic* in the template. * `step.inputName`, `choice.inputValue` - name/value for the form input. Whether a select or radio is used is up to the template. * `choice.image` - product or variant image (typically a product photo), not available everywhere * `choice.icon` - image uploaded for the attribute set (typically an icon) * `choice.color` - HEX color code set on the attribute value ```twig {%for step in product.variantChoiceSteps%}
{%if step.mode == 'icons'%}
{{step.headline}}
{%else%}
{%if step.headline%}{{step.headline}}{%else%}{%trans%}Vyberte variantu{%endtrans%}{%endif%}
{%endif%}
{%endfor%}
``` --- --- 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 \
  • 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 ``` **Explicit GDPR form codes** ```twig ``` Generated output: ```html
    gdprForm({"codes":["newsletter_consent","marketing"]})
    ``` ## 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 ``` Generated output: ```html
    gdprForm({"context":"newsletter-signup"})
    ``` **Newsletter form with extra fields and redirect** ```twig ``` Generated output: ```html
    gdprForm({"context":"newsletter-signup"})
    ``` ## 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 variable `q` with the search query. Each result link must have `tabindex` and `data-result-type` (product, category, brand, more) attributes. Style the `search-current-item` CSS class for keyboard navigation highlight. ::: ::: info Fires `autocomplete:loaded` event on the form element after each autocomplete results load. ::: **Search form with autocomplete** ```twig ``` Generated output: ```html
    ``` ## 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 ``` Generated output: ```html
    ``` **Product review with all fields** ```twig ``` Generated output: ```html
    ``` ## 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 {{product.name}}

    No products found.

    No products match your filter.

    ``` Generated output: ```html

    trans:No products found.

    ``` ## 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

    {{product.name}}

    {{_self.item_product_default(product)}} ``` Generated output: ```html

    name

    ``` ## 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}) | | 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 {{product.name}}No products found
    ``` Generated output: ```html
    abc
    ``` **Custom product listing with category filter** ```twig
    No products
    ``` ## 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 Load more ``` ## 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
    {{ kategorie.name }}
    ``` ## 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 | **Responsive hero banner with Swiper plugin** ```twig ``` Generated output: ```html ``` ## 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
      {% for category in repository.category.findBy({'parent': 0}) %}
    • {{ category.name }} {% if not lazy and category.subcategories is not empty %} {% endif %}
    • {% endfor %}
    ``` ## 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
    ``` Generated output: ```html
    trans:hello
    ``` **Sidebar menu rendered as list** ```twig
    ``` ## 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 {% for transport in cart.availableTransportMethods %}{{ transport.name }}{% endfor %} ``` Generated output: ```html
    PPL
    ``` ## 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 {{ transport.name }} ``` Generated output: ```html ``` ## 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 {% for payment in cart.availablePaymentMethods %}{{ payment.name }}{% endfor %} ``` Generated output: ```html
    ``` ## 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 {{ payment.name }} ``` Generated output: ```html ``` ## 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: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 Reset filters {% for attr in attributes %} {% endfor %} ``` ## 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: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
    {% for step in product.variantChoice.steps %}
    {{ step.name }}: {% for value in step.values %} {% endfor %}
    {% endfor %}
    ``` ## 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 (lazy on page load), manual | **Cart summary that updates dynamically** ```twig Order #{{ order.id }} ``` Generated output: ```html
    Order #7
    ``` ## 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 ``` Generated output: ```html description ``` **Lazy-loaded lifestyle image variant** ```twig ``` **SVG brand logo** ```twig ``` --- --- url: /config.md --- # Configuration Every template can contain a **config.json** file describing template properties. Some properties are automatically generated (*plugins*, ...) and others can be modified manually. ## Configuration Reference **config.json Attributes** | Attribute | Type | Default | Description | |---|---|---|---| | **responsive** | *bool* | **false** | template is responsive -- activates viewport meta tag | | **webfonts** | *array* | **\[]** | list of Google WebFonts | | **webfontsPreload** | *array* | **\[]** | list of Google WebFonts to preload | | **features** | *array* | **\[]** | list of supported special features (*last\_viewed\_products*) | | **cartSteps** | *array* | **\[]** | checkout steps in the order they appear | | **typekit** | *string* | --- | Typekit font ID | | **skipOrderSummary** | *bool* | **false** | skip the *Checkout summary* step | | **countryBeforeDelivery** | *bool* | **false** | delivery country is selected in the step before delivery method selection | | **wysiwyg** | *object* | {} | list of CSS classes (and CSS rules) used in the WYSIWYG editor | | **settings** | *object* | {} | admin-configurable template settings definition | | **fulltext** | *bool* | **true** | allow search engine indexing | | **mobile** | *bool* | **false** | template is mobile-only | | **viewportDesktop** | *int* | --- | viewport width when forcing desktop version on a responsive layout | | **skipOnFirstPage** | *object* | --- | how many listing items to **skip** on the first page of listings | | **limitOnFirstPage** | *object* | --- | how many listing items to **display** on the first page of listings | | **inputInLabel** | *bool* | **true** | wrap *checkbox*/*radio* inside *label* in generated forms | | **manifest** | *object* | --- | definition for [manifest.json](https://developer.mozilla.org/en-US/docs/Web/Manifest) | | **allowedPagination** | *array* | --- | list of allowed pagination size values | | **criticalCss** | *object* | --- | per-page-type critical CSS generation hints (forceInclude, forceExclude, viewports) | | **variantChoice** | *object* | --- | supported variant selection types | ### features * *last\_viewed\_products*: visited products are stored so they can be listed in the template * *multiline\_menu\_items*: category names for the menu can be saved with line breaks in the admin * *upsell\_customer\_note*: upsell supports customer note input * *upsell\_customer\_upload*: upsell supports customer file upload * *google\_recaptcha\_enterprise\_cart*: Google reCaptcha Enterprise in the cart ### cartSteps If the shop uses multiple checkout steps (visible in the URL as e.g. `/Kosik?sekce=adresa`), they must be listed in the configuration. The `/Kosik` (cart) and `/Pokladna` (checkout) paths themselves are not listed -- only the additional query parameters. Each step specifies a code identifying the content of that particular step (content, delivery, address, summary). ```json { "cartSteps": [ { "handles": [ "content" ] }, { "handles": [ "delivery" ], "query": "dodani" }, { "handles": [ "address" ], "query": "adresa" } ] } ``` ### fields Text types defined in the config are displayed as text fields in the admin panel. Once filled in, they are available in the template via `object.text('vlastni_kod_textu')` (custom text code). The config also specifies the field label shown in the admin and a description of where the text appears and any constraints it may have. Supported objects: **transport**, **paymentMethod**, **textPage**, **store**, **admin**, **category**, **brand**, **product**, **author**, **upsellGroup**, **article** Supported formats: **html**, **textarea**, **text** ```json { "fields": { "transport": { "text": { "short" : { "label": "Krátký popisek", "format": "html", "description": "Zobrazuje se košíku po najetí myší na způsob dopravy" }, "long" : { "label": "Dlouhý popis", "format": "html", "description": "Zobrazuje se na informační stránce a může mít 2-3 odstavce" } }, "image": { "ico" : { "label": "Ikonka výběru", "description": "Zobrazuje se košíku po najetí myší na způsob dopravy" } } } } } ``` ### criticalCss Per-page-type hints for the critical CSS generator. Each key is a page type matching the types returned by `/_random`: | Page Type | Description | |---|---| | **index** | Homepage | | **product** | Product detail (random products are sampled) | | **category** | Top-level category listing | | **subcategory** | Nested category listing | | **page** | Static text pages | Each type supports the following options: | Option | Type | Description | |---|---|---| | **forceInclude** | *array* | CSS selectors always included in the output (substring match) | | **forceExclude** | *array* | CSS selectors always excluded from the output (substring match) | | **viewports** | *object* | Override default viewport sizes for this type | Matching is substring-based — `".main-banner"` matches `.main-banner`, `.main-banner__slide`, `.page .main-banner`, etc. The following selectors are always applied regardless of config: * **Always included:** `.no-js`, `.global-announcement` (and BEM variants) * **Always excluded:** `.wf-active` (web fonts loaded after critical render) Default viewports are **mobile: 380×650** and **desktop: 1000×600**. Per-type viewports override these for that type only: ```json { "criticalCss": { "index": { "forceInclude": [".main-banner", ".hero-slider"], "forceExclude": [".video-player"] }, "product": { "forceInclude": [".product-gallery"], "viewports": { "mobile": { "width": 414, "height": 896 } } } } } ``` ## Configuration Example ```json { "webfonts":[ "Open Sans:400,600,700" ], "webfontsPreload":[ "Open Sans:400,700" ], "responsive": true, "wysiwyg": [ { "name": "important paragraph", "css": "p.wysiwyg_important {color: #F5007E;}" }, { "name": "small paragraph", "css": "p.wysiwyg_small {font-size:0.9em}" } ], "skipOnFirstPage": { "categoryProducts": 1, "filteredCategoryProducts": 0, "topicArticles": 1, "allArticles": 2 }, "limitOnFirstPage": { "categoryProducts": 1, "filteredCategoryProducts": 0, "topicArticles": 1, "allArticles": 2 }, "manifest": { "display": "standalone", "background_color": "#ff00ff", "theme_color": "#ff00ff", "tile_color": "#ff00ff", }, "settings": { "menu_mode": { "label": "Režim menu", "context": "global", "default": "small", "type": "select", "group": "Levý sloupec", "values": { "small": "malé menu", "big": "velké menu" } } }, "criticalCss": { "index": { "forceInclude": [".main-banner"], "forceExclude": [".video-player"] }, "product": { "forceInclude": [".product-gallery"] } }, "variantChoice": { "code": "default", "modes": { "dropdown": "roletka s názvy", "icons": "obrázky s názvem" } } } ``` ## Settings The *settings* section lets you define template values that can be configured directly from the admin panel. Use cases include toggling visual elements (e.g. winter theme), setting footer links, or selecting a display mode per category. **context** | Value | Description | |---|---| | **global** | available across the entire shop | | **category** | category-specific, configured separately for each category | | **product\_category** | configured separately for each category, applied on the product detail page | **type** | Value | Description | |---|---| | **select** | generic dropdown | | **string** | text (single line, no HTML) | | **color** | color (hex code) | | **email** | e-mail | | **text\_page** | text page | | **article** | article | | **article\_topic** | article topic | | **banner\_section** | banner section | | **upsell\_group** | upsell group | | **attribute\_group** | attribute group | | **attribute** | attribute | | **attribute\_article** | article attribute | | **attribute\_store** | store attribute | | **attribute\_customer** | customer attribute | | **category** | category | | **product** | product | | **product\_list** | product selection by properties | | **product\_rules** | product group rules | | **url** | any shop URL (including link text and title) | | **admin** | shop administrator | | **storage\_center** | storage center | | **store** | store | | **working\_hours** | opening hours | | **image** | uploaded image (SVG/PNG/JPG/...) | Configuration example: ```js { "settings": { "sibebar_mode": { "label": "Režim postraního panelu", "description": "Přepínání způsobu zobrazení levého sloupce webu", "context": "global", "default": "small", "group": "Levý sloupec", "type": "select", "values": { "small": "malé menu", "big": "velké menu" } }, "category_mode": { "label": "Režim zobrazení kategorie", "context": "category", // configured separately for each category "default": "default", "type": "select", "values": { "default": "standardní zobrazení", "big_banner": "zobrazit s velkým bannerem" } }, "product_mode": { "label": "Režim zobrazení produkt", "context": "product_category", // configured separately for each category, applied on product detail "default": "default", "type": "select", "values": { "default": "standardní zobrazení", "big_banner": "zobrazit s velkým bannerem" } }, "footer_menu": { "label": "Položky patičky", "context": "global", // site-wide "type": "text_page", // select a text page "lang": true, // configured separately for each language -- adding lang:true hides the originally set value "multiple": true // select multiple pages with ordering (the template receives an array of pages) } } } ``` Usage in the template: ```twig {% if this.templateAttributes.sibebar_mode == 'small' %} malý sidebar {% endif%} ``` ## Master Template A master template serves as a base for other templates. It defines a list of variables that child (slave) templates can override. Configuration example: ```json { "master" : { "tpl": { "phone": { "name": "Telefon", "default": "+420 333 222 111" }, "email": { "name": "E-mail", "type": "email", "language": true, "default": "info@example.org" }, "facebook": { "name": "Facebook URL", "type": "url" }, "showRelevantItems": { "name": "Zobrazit nabídku podobných produktů", "type": "bool" }, "pageFooterId": { "name": "Text v patičce", "type": "page" }, "listStyle": { "name": "Stype výpisu produktů", "type": "select", "values" : { "simple": "jednoduchý", "large_image": "velké obrázky", } } }, "sass": { "primary-color": { "name": "Hlavní odstín", "type": "color", "default": "#fff", "group": "Barvy" } }, "files": { "banner.png": "Obrázek v hlavičce" }, "languageFiles": { "logo.png": "Logo eshopu" } } } ``` To use variables in SASS, add the line `////` anywhere in *settings.scss*. During compilation this line is replaced with the configuration variables. ```scss $pagination-mobile-items: true; $pagination-arrows: false; //// ``` A slave template is activated by deleting all files and adding a **slave.json** file: ```json { "master": "master-template-name" } ``` ## Complete Production Config Example A realistic config.json extracted from the buran production template, showing all major sections working together. The `master` section defines variables that child (slave) templates can override -- `tpl` for feature toggles accessible via `this.slaveConfig`, and `sass` for SCSS variables organized into groups (Základní nastavení, Tlačítka, Štítky, Oznámení): ```json { "webfonts": [ "Manrope:500,600,700" ], "responsive": true, "plugins": { "jquery": "3.4", "foundation": "6.4", "swiper": "6.x", "jquery-fancybox": "3.5", "jquery-validation": "1.19", "jquery-ui-slider": "2.0", "jquery-ui-dialog": "2.0", "svgxuse-polyfill": "1.2" }, "skipOrderSummary": true, "cartSteps": [ { "handles": ["content"] }, { "handles": ["delivery"], "query": "dodani" }, { "handles": ["address"], "query": "adresa" } ], "inputInLabel": false, "features": [ "last_viewed_products", "syntax2019", "upsell_customer_note", "upsell_customer_upload" ], "variantChoice": { "code": "default", "modes": { "selects": "roletka", "icons": "barevná okýnka", "text": "textově (default)" } }, "manifest": { "theme_color": "#0F172A" }, "master": { "name": "buran", "tpl": { "enableCompare": { "label": "Povolit porovnání", "type": "bool" }, "enableWishlist": { "label": "Povolit Seznam přání", "type": "bool" }, "enableLoyaltyProgram": { "label": "Povolit věrnostní program", "type": "bool" }, "enableUpsells": { "label": "Povolit upselly", "type": "bool" }, "enableGifts": { "label": "Povolit Dárky v košíku", "type": "bool" }, "enableShops": { "label": "Povolit prodejny", "type": "bool" }, "enableComments": { "label": "Povolit komentáře u produktů", "type": "bool" }, "enableReviews": { "label": "Povolit recenze (Heureka, Zbozi, Biano)", "type": "bool" } }, "sass": { "config-primary-color": { "label": "Barva textu - hlavní (primary) barva", "default": "#10b981", "group": "Základní nastavení" }, "config-success-color": { "label": "Barva textu - hlavní (success) barva", "default": "#10b981", "group": "Základní nastavení" }, "config-button-background": { "label": "Základní tlačítko - barva pozadí", "default": "#10b981", "group": "Tlačítka" }, "config-button-background-hover": { "label": "Základní tlačítko - barva pozadí po najetí myši", "default": "#059669", "group": "Tlačítka" }, "config-order-button-background": { "label": "Tlačítko nákupu - barva pozadí", "default": "#10b981", "group": "Tlačítka" }, "config-product-label-discount-bg": { "label": "Štítek Sleva - barva pozadí", "default": "#dc2626", "group": "Štítky" }, "config-product-label-new-bg": { "label": "Štítek Novinka - barva pozadí", "default": "#f5f5f5", "group": "Štítky" }, "config-global-announcement-info-background-color": { "label": "Informace - barva pozadí", "default": "#f0f8ff", "group": "Oznámení" }, "config-global-announcement-default-background-color": { "label": "Oznámení - barva pozadí", "default": "#cbe3c6", "group": "Oznámení" } // ... more color variables for labels, discount badges, etc. } } } ``` The `tpl` feature toggles are accessed in templates via `this.slaveConfig`: ::: v-pre ```twig {% if this.slaveConfig.enableWishlist %} {# Show wishlist button #} {% endif %} ``` ::: ## Plugins Reference Plugins are declared in the `plugins` section of config.json. The platform automatically loads the corresponding JavaScript and CSS for each declared plugin. All plugins listed below are used in the buran production template: | Plugin | Version | Description | |---|---|---| | **jquery** | 3.4 | jQuery library -- required by most other plugins | | **foundation** | 6.4 | Zurb Foundation responsive framework (grid system with `grid-x`/`cell`, responsive visibility classes like `show-for-medium`, `hide-for-large`) | | **swiper** | 6.x | Touch-enabled slider/carousel for banners, product carousels, and subcategory strips | | **jquery-fancybox** | 3.5 | Lightbox for product image galleries and modal popups | | **jquery-validation** | 1.19 | Client-side form validation (checkout forms, registration, contact) | | **jquery-ui-slider** | 2.0 | Range slider widget used for price filters (see [filter](filter.md)) | | **jquery-ui-dialog** | 2.0 | Modal dialog component (login, quick-view, confirmations) | | **svgxuse-polyfill** | 1.2 | Polyfill for external SVG `` references in older browsers | ## Custom Fields Usage in Templates Fields defined in the `fields` section of config.json (see [fields](#fields) above) provide custom text and image slots for various objects. Once defined, they can be filled in through the admin panel and accessed in templates. **Accessing custom text fields:** ::: v-pre ```twig {# Display short description of a transport method #} {{ transport.text('short') }} {# Display custom HTML field on a product #} {{ product.text('specification') }} {# Use custom text in conditionals #} {% if category.text('long') %}
    {{ category.text('long') }}
    {% endif %} ``` ::: **Accessing custom image fields:** ::: v-pre ```twig {# Display transport icon as a 40x40 image #} {# Display category banner image #} {# Category icon used in subcategory listing #} ``` ::: **Defining fields in config.json:** The `fields` config specifies text and image slots per object type. The `label` appears in the admin form, `format` controls the editor type, and `description` provides admin guidance: ```json { "fields": { "transport": { "text": { "short": { "label": "Krátký popisek", "format": "html", "description": "Zobrazuje se v košíku" } }, "image": { "ico": { "label": "Ikonka výběru", "description": "Zobrazuje se v košíku" } } }, "category": { "text": { "long": { "label": "Úvodní text", "format": "html", "description": "Text nad výpisem" } }, "image": { "ico": { "label": "Ikonka kategorie", "description": "Pro podkategorie" } } } } } ``` Supported objects for custom fields: **transport**, **paymentMethod**, **textPage**, **store**, **admin**, **category**, **brand**, **product**, **author**, **upsellGroup**, **article** ## Critical CSS Critical CSS is the minimal set of styles needed to render the visible part of a page (above the fold). These styles are inlined directly in the ``, so the browser can paint the page without waiting for the full stylesheet to download. After the full CSS loads (typically under 1 second), the page appearance is corrected. ### How generation works During deployment, the generator: 1. Fetches a list of random URLs per page type from `/_random` (index, product, category, subcategory, page) 2. Downloads the full `package.min.css` 3. For each URL × device (mobile 380×650, desktop 1000×600): * Opens the page in headless Chromium with JavaScript blocked * Parses the full CSS and checks which selectors have elements **above the viewport fold** * Removes `@media print`, media queries for smaller viewports, unused `@keyframes` * Keeps rules for `position: fixed`/`sticky` elements (always visible) * Applies `forceInclude`/`forceExclude` from config 4. Merges results across URLs, deduplicates, and runs cleanup (removes transitions, vendor prefixes, `@font-face`, `background-image`) The output is a JSON object with `css` and `mobileCss` per page type, stored in the template config and served inline. ### Size budget Critical CSS should stay **under ~14 KB gzipped** to fit within the first TCP roundtrip (congestion window). Larger critical CSS delays First Contentful Paint instead of helping it. If your critical CSS is too large, use `forceExclude` to remove heavy selectors that aren't needed for the initial render. ### When to configure `criticalCss` **No configuration needed** for most templates. The generator samples random URLs and detects above-fold elements automatically. You only need `criticalCss` in config.json when: * **Conditional elements** — A component only appears on some pages (e.g., `.main-banner` on the homepage with a banner, `.product-gallery` only on products with multiple images). If the sampled page happens not to have it, the styles will be missing. Use `forceInclude`. * **Heavy below-fold components** — A large component (e.g., `.video-player`, `.reviews-section`) is technically above the fold on some pages but is not essential for the first paint. Use `forceExclude` to save bytes. * **Custom viewport needs** — A page type has a non-standard layout that requires different viewport dimensions for accurate extraction. ### Recommended patterns **Homepage with banners/sliders:** ```json { "criticalCss": { "index": { "forceInclude": [".main-banner", ".hero-slider"], "forceExclude": [".video-player", ".chat-widget"] } } } ``` **Product page with conditional gallery:** ```json { "criticalCss": { "product": { "forceInclude": [".product-gallery", ".product-price"] } } } ``` **Multiple types sharing the same pattern:** ```json { "criticalCss": { "category": { "forceInclude": [".category-banner"] }, "subcategory": { "forceInclude": [".category-banner"] } } } ``` ### Testing critical CSS * **Quick flash test:** On production, press Ctrl+F5. Critical styles render first, then the full CSS loads. The flash should be brief and the layout should not jump. * **Force critical mode:** Append `?_forceCritical` to any URL (requires admin login). The page renders using only the critical CSS — this shows exactly what visitors see before the full stylesheet loads. * **Disable web fonts:** Append `?_disableWebFonts` to see the page without web fonts, matching how the generator sees it. * **PageSpeed Insights:** [Google PageSpeed](https://developers.google.com/speed/pagespeed/insights/) gives the most realistic performance assessment. ## Favicon The favicon is not part of the template. It is uploaded in the shop admin separately for each domain. --- --- url: /content-grid.md --- # Content grid For structured content, you can define a schema that controls which UI elements are displayed in the admin panel. This allows creating an interface for entering content that the admin panel would not support by default. This feature is intended for presentational elements -- for example, displaying text on a landing page at specific positions together with images. The content is not accessible outside the template, so the feature is not suitable for content that will be needed in feeds, label printing, and similar use cases. ## Schema definition The schema is an XML file in the **content-grid** directory inside the template. The file name determines how the content is referenced in the template (e.g., content defined by the file **content-grid/product-cta.xml** is accessed in the template using {{ product.contentGrid('product-cta') }}). In the editor, you can import the schema https://tpldoc.simplia.cz/xsd/content-grid.xsd to enable autocompletion of possible values and validation of correct syntax. The defined structure does not directly affect functionality and is primarily for better orientation in the admin panel. The layout should ideally approximate the final display in the shop. ```xml Obsahová mřížka 1 false full_page ``` ## Display mode The *mode* tag setting switches between two display modes: * *full\_page*: The admin panel shows a "Layout" dropdown for selecting a page type. After selection, the dropdown disappears and no buttons for adding additional content grids are shown. Ideal for typed pages such as an "About Us" page, etc. * *part*: Assumes that multiple different content grids may be used on a single page. Typically, a content grid represents one content element but does not take over the rest of the page. ## Usage in templates For supported objects, content is always loaded using the *contentGrid* method with the definition file name. If no content has been defined for the given object, *null* is returned. The structure of the returned data corresponds to the XML definition structure. Not all values need to be filled in. ::: v-pre ```twig {% if product.contentGrid('product-cta') %}

    {{ product.contentGrid('product-cta').header.headline }}

    {% for content in product.contentGrid('product-cta').content2 %} {{ content.text }} {% endfor %} {% endif %} ``` ```twig {% if this.contentGrid('banner-cta') %}

    {{ this.contentGrid('banner-cta').header.headline }}

    {% for content in this.contentGrid('banner-cta').content2 %} {{ content.text }} {% endfor %} {% endif %} ``` ::: ## Content Type Reference ::: v-pre | Type | Description | Template Access | |---|---|---| | **string** | Single-line plain text input | `{{ row.code }}` -- renders as plain string | | **html** | Rich text editor (full WYSIWYG) | `{{ row.code }}` -- renders as raw HTML | | **html\_simple** | Simplified rich text editor (basic formatting only -- bold, italic, links) | `{{ row.code }}` -- renders as raw HTML | | **image** | Image upload (PNG, JPG, SVG, ...) | `` | | **color** | Color picker (hex code) | `{{ row.code }}` -- renders as `#rrggbb` string | | **url** | URL with link text and title | `{{ row.code.url }}`, `{{ row.code.label }}`, `{{ row.code.title }}` | | **category** | Category selector from catalog tree | Returns a category object with `.url`, `.name`, etc. | | **product** | Single product selector | Returns a product object | | **product\_list** | Product selection by rules (category, attributes, etc.) | Returns a collection for [i:list-container](list.md) `collection` attribute | | **select** | Dropdown with predefined `` child elements | `{{ row.code }}` -- renders the selected choice `code` value | | **article** | Article selector | Returns an article object with `.url`, `.name`, etc. | | **text\_page** | Text page selector | Returns a text page object with `.url`, `.name`, `.text` | ::: All types support the `multiple="true"` attribute, which turns the value into a reorderable array in the admin. Rows with `multiple="true"` are accessed as arrays in Twig (use `{% for item in ... %}`). ## Production Examples These examples are extracted from the buran production template and simplified (feature toggles removed) to show core patterns. ### 1. Simple: Footer USP icons A repeatable row with an image and short text. Used for "Unique Selling Proposition" icons displayed as a horizontal bar above the footer. The `html_simple` type provides basic formatting (bold, italic, links) without full WYSIWYG. **Schema** (`content-grid/footer-usp.xml`): ```xml Patička - USP true part ``` **Template** (`_footer-usp.tpl`) -- the actual buran template renders USP items in a flex row with SVG icon support: ::: v-pre ```twig {# _footer-usp.tpl -- loaded from _footer.tpl via {% include '_footer-usp.tpl' %} #} {% set items = this.contentGrid('footer-usp').item %}
    {% for item in items %}
    {# vector + transparent flags enable SVG rendering without background #}
    {{ item.text }}
    {% endfor %}
    ``` ::: The parent `_footer.tpl` conditionally includes this partial: ::: v-pre ```twig {% if this.contentGrid('footer-usp').item %} {% include '_footer-usp.tpl' %} {% endif %} ``` ::: ### 2. Medium: Footer column links with nested URL loops Each column has a headline (string) and repeatable URL links. The `url` type provides both `.url` and `.label` properties. Note the nested `multiple="true"` -- the outer row repeats columns, and the inner `link` content repeats links within each column. **Schema** (`content-grid/footer-column-links.xml`): ```xml Patička - Sloupečky s odkazy true part ``` **Template** (`_footer.tpl` excerpt) -- the buran footer limits to 3 columns and renders links as a `
      ` list with collapsible headings on mobile: ::: v-pre ```twig {# _footer.tpl -- iterate footer columns (limited to first 3) #} {% for item in this.contentGrid('footer-column-links').item %} {% if loop.index <= 3 %} {% endif %} {% endfor %} ``` ::: ### 3. Complex: Homepage banner with products A complex schema using nested `` elements to group related fields into a two-column admin layout. Each banner has a left side (background color + image, 30% width) and a right side (text + headline + description + button + product list, 70% width). The `product_list` type returns a collection that can be passed to [i:list-container](list.md). **Schema** (`content-grid/homepage-banner-products.xml`): ```xml Homepage - Banner s produkty true part ``` **Template** (`index.tpl` excerpt) -- the buran homepage iterates banner items, accesses nested cell data via `item.left.*` and `item.right.*`, and passes the `product_list` collection to a product slider: ::: v-pre ```twig {# index.tpl -- homepage banner with product carousel #} {% if this.contentGrid('homepage-banner-products') %} {% set section = this.contentGrid('homepage-banner-products').item %} {% for item in section %} {% endfor %} {% endif %} ``` ::: --- --- url: /glossary.md --- # Czech-English Glossary This glossary maps Czech identifiers used in Simplia template code to their English meanings. All terms listed here are **immutable code identifiers** — they appear in filenames, variable names, URL paths, filter names, and function names that cannot be renamed. > **For AI agents:** When you encounter a Czech identifier in template code, look it up here. The "Code identifier" column shows the exact string found in source files. Do not translate or rename these identifiers. ## Template Files Each e-shop page has a corresponding `.tpl` template file. The filenames use Czech naming. | Code identifier | English meaning | Purpose | |---|---|---| | `_base.tpl` | base layout | Main layout wrapper (header, footer, page structure) | | `index.tpl` | homepage | Homepage template | | `zbozi.tpl` | product detail | Single product page | | `kosik.tpl` | shopping cart | Cart page (may contain multiple checkout steps) | | `kosik_obsah.tpl` | cart content | Cart content partial | | `objednavka.tpl` | order detail | Order detail page (logged-in user viewing past order) | | `objednavky.tpl` | order list | List of customer orders | | `potvrzeni.tpl` | order confirmation | Order confirmation / thank-you page | | `rychlokos.tpl` | quick cart | Zero-step (single page) checkout | | `dodani.tpl` | delivery | Delivery/shipping selection template | | `adresa.tpl` | address | Address entry template | | `registrace.tpl` | registration | User registration page | | `prihlaseni.tpl` | login | User login page | | `profil.tpl` | user profile | Logged-in user profile page | | `hledat.tpl` | search | Search page | | `vyhledavani.tpl` | search results | Search results page | | `vypis.tpl` | product listing | Category product listing page | | `vypis_filtr.tpl` | filtered listing | Filtered product listing (AJAX) | | `vypis_porovnani.tpl` | product comparison | Product comparison page | | `vypis_prani.tpl` | wishlist | Wishlist page | | `vypis_vyrobce.tpl` | brand listing | Brand/manufacturer product listing | | `vyrobce.tpl` | brand detail | Single brand/manufacturer page | | `znacky.tpl` | brands list | All brands page | | `clanek.tpl` | article detail | Single article/blog post page | | `clanek_zbozi.tpl` | article products | Article-related products page | | `novinky.tpl` | news list | News/blog listing page | | `novinka.tpl` | news detail | Single news item page | | `tema.tpl` | topic detail | Article topic/magazine section page | | `temata.tpl` | topics list | Article topics listing page | | `autor.tpl` | author | Author page | | `recenze/vypis.tpl` | reviews listing | Customer reviews page | | `stranka.tpl` | text page | Static/CMS text page | | `prodejna.tpl` | store detail | Physical store/branch detail page | | `mapa.tpl` | sitemap | Site map page | | `videa.tpl` | videos | Video listing page | | `dialog/kosik_ok.tpl` | cart success dialog | "Added to cart" confirmation dialog | | `dialog/hlidaci_pes.tpl` | watchdog dialog | Price/availability watchdog dialog | | `_listing.tpl` | listing definitions | Listing item, pagination, and empty-state macros | | `_listing_article.tpl` | article listing | Article listing macros | | `_filter.tpl` | filter | Product filter template | | `_menu.tpl` | menu | Navigation menu template | | `ajax/search.tpl` | AJAX search | AJAX search results partial | ## URL Presenters (iUrl paths) The `iUrl()` function generates internal URLs. The first argument is a presenter path in Czech. ### Order flow | Code identifier | English meaning | |---|---| | `Objednavka/Kosik` | order/cart | | `Objednavka/Pokladna` | order/checkout (summary/review) | | `Objednavka/Platba` | order/payment | | `Objednavka/Rychlokos` | order/quick-cart (single-page checkout) | | `Objednavka/Vypis` | order/list (order history) | ### Product listings | Code identifier | English meaning | |---|---| | `Vypis/Vybrane` | listing/selected (products by ID) | | `Vypis/Porovnani` | listing/comparison | | `Vypis/Akce` | listing/promo (products on promotion) | | `Vypis/AkceSleva` | listing/promo-discount | | `Vypis/Slevy` | listing/discounts | | `Vypis/SlevyVyprodej` | listing/discounts-clearance | | `Vypis/Vyprodej` | listing/clearance | | `Vypis/Skladem` | listing/in-stock | | `Vypis/Cena` | listing/price (price range) | | `Vypis/Nejnovejsi` | listing/newest | | `Vypis/Nejprodavanejsi` | listing/bestsellers | | `Vypis/VyrobceKategorie` | listing/brand-category | | `Vypis/Vyhledavani` | listing/search | ### User | Code identifier | English meaning | |---|---| | `User/Login` | user/login | | `User/Registrace` | user/registration | | `User/HesloPozadavek` | user/password-request (forgotten password) | | `Wish/Vypis` | wishlist | ### Other | Code identifier | English meaning | |---|---| | `Vyrobce/Vypis` | brand/list | | `Tema/Vypis` | topic/list (magazine) | | `Recenze/Vypis` | review/list | | `Novinka/Vypis` | news/list | | `Video/Vypis` | video/list | | `Page/Mapa` | page/sitemap | ## URL Parameters Czech parameter names used in `iUrl()` calls. | Code identifier | English meaning | Context | |---|---|---| | `sekce` | section | Cart step section (`address`, `delivery`, ...) | | `kategorie` | category | Category link object | | `vyrobce` | brand/manufacturer | Brand link object | | `vyber` | selection | Product IDs (hyphen-separated) | | `fraze` | phrase | Search query string | | `datum` | date | Date filter | | `od` | from | Price range start | | `do` | to | Price range end | | `objednavka` | order | Order reference for payment | ## Twig Filters (Czech-named) Custom Simplia filters with Czech names. See [Twig reference](/twig) for full details. | Code identifier | English meaning | Description | |---|---|---| | `cena` | price | Format number as price with currency | | `mena` | currency | Format number with currency (alias of cena) | | `mena_rounded` | currency rounded | Rounded price with currency | | `orez` | trim/truncate | Truncate text to given length | | `prvniVelke` | capitalize first | Uppercase first letter | | `diakritika` | diacritics removal | Strip diacritical marks | ## Twig Functions (Czech-named) | Code identifier | English meaning | Description | |---|---|---| | `isPracovniDen()` | isWorkingDay | Check if a date is a working day | | `pristiPracovniDen()` | nextWorkingDay | Get the next working day | ## Global Context Variables Variables available in all templates. | Code identifier | Type | Description | |---|---|---| | `MENA` | string | Current currency code (e.g., `CZK`) | | `MENADEF` | string | Default currency code | | `HOST` | string | Current domain hostname | | `LANG` | string | Current language code | | `URI` | string | Current request URI | | `cart` | Order | Current shopping cart object | | `customer` | Customer | Current logged-in customer (or guest) | | `shop` | Shop | Shop configuration object | | `repository` | Repositories | Data access layer for querying entities | | `this` | TemplateContext | Current template context (settings, URLs, content grid) | ## Template Context Variables (per-file) Czech variable names used as Twig context in specific template files. | Code identifier | English meaning | Available in | Type | |---|---|---|---| | `zbozi` | product | `i:product-box` tag, `zbozi.tpl` | Product | | `kategorie` | category | list source expressions | Category | | `article` | article | `clanek.tpl` | Article | | `actuality` | news item | `novinka.tpl` | Actuality | | `store` | physical store | `prodejna.tpl` | Store | | `textPage` | text page | `stranka.tpl` | TextPage | | `order` | order | `objednavka.tpl`, `potvrzeni.tpl` | Order | | `brand` | brand | `vyrobce.tpl` | Brand | | `searchQuery` | search query | `vyhledavani.tpl` | string | | `deliveryCountries` | delivery countries | `kosik.tpl` | Country\[] | ## Pagination Properties | Code identifier | English meaning | Description | |---|---|---| | `pagination.stran` | page count | Total number of pages | | `pagination.nsum` | total count | Total number of items | | `pagination.strany` | pages HTML | Rendered pagination links | ## Config Concepts | Term | Description | |---|---| | master template | Parent template that defines overridable variables for child templates | | slave template | Child template that inherits from a master, overriding specific variables via `slave.json` | | `cartSteps` | Array in `config.json` defining multi-step checkout flow | | `cartSteps[].handles` | What the step contains: `content`, `delivery`, `address`, `summary` | | `cartSteps[].query` | Czech URL parameter for the step (e.g., `dodani`, `adresa`) | | `variantChoice` | Configuration for product variant selection UI modes | | `this.nastaveni` | Access to admin-configured template settings (nastaveni = settings) | | `this.templateAttributes` | Access to template appearance settings | | `sekce_kosik` | Flag/variable indicating current cart section | --- --- url: /delivery.md --- # Delivery Method The delivery selection template is undergoing gradual unification, simplification, and migration to components. The goal is to introduce newer approaches incrementally without requiring a full rewrite of all templates from the start. In the examples below, the `i:****` tags are the important parts; the rest of the template will differ historically for each shop. ## Delivery-transport and delivery-branch Defines the element for selecting a carrier. Typically this is a radio input with a label such as *Ceska posta* (Czech Post). Branch selection is displayed only if the given delivery method supports branches (this is not handled in the template). The branch selection button text is optional and is automatically added to translations. The reload-targets attribute accepts selectors for elements that should be automatically updated when the transport or payment selection changes. ```twig {% for transport in transports %} {% endfor %} ``` ```html
      Drtinova 10, Praha vyberte pobočku
      ``` ## Delivery-payment Defines the element for selecting a payment method. Typically this is a radio input with a label such as *platba kartou* (card payment). The tag identifies the related element for a single carrier -- currently only branch selection. Available payment options are automatically updated when the transport selection changes. ```twig {% for payment in payments %} {% endfor %} ``` ```html
      ``` ## Delivery-map Defines an element with an interactive carrier map. ```twig ``` **Attributes of delivery-map** | Attribute | Required | Description | |---|---|---| | country | *no* | Country | | carrier | *no* | Carrier type | | query | *no* | Search phrase | ```html
      ``` ## Complete Delivery Step The delivery step template (`kosik_dodani.tpl`) wraps the transport and payment selection in a form with step navigation. It is loaded by the checkout router (`kosik.tpl`) when `sekce == 'dodani'` (see [Cart - Checkout Flow Architecture](/cart#checkout-flow-architecture)). ```twig {# kosik_dodani.tpl — delivery step #}
      {# Step indicator breadcrumb — kosik_kroky.tpl #} {% include 'kosik_kroky.tpl' %} {# Optional info banner from content grid #} {% if this.contentGrid('checkout-delivery-info') %} {% set block = this.contentGrid('checkout-delivery-info').checkouttext %}
      {{ block.text }}
      {% endif %}

      {% trans 'checkout.headline' %} ({{ cart.catalogItemTotalAmount }} {% trans 'template.qty' %})

      {# Transport and payment selection (shared partial) #} {% include '_dodani.tpl' %}
      {# Order summary sidebar — receives checkout context #} {% include 'kosik_souhrn.tpl' with {'checkout': 'delivery'} %}
      {# Hidden field to advance to address step on submit #}
      ``` ### Key Structural Elements | Element | Purpose | |---|---| | `kosik_kroky.tpl` | Step progress breadcrumb, included at the top of each step | | `_dodani.tpl` | Shared partial containing the `i:delivery-transports` and `i:delivery-payments` components documented above | | `kosik_souhrn.tpl` | Order summary sidebar, receives `{'checkout': 'delivery'}` context to show delivery costs and a "Continue to address" button | | `toSekce` hidden field | Set to `udaje` (address) to advance to the next checkout step on form submit | | `contentGrid('checkout-delivery-info')` | Optional admin-configurable info banner (see [Content Grid](/content-grid)) | | `i:dynamic="cart"` | Marks the item count span for automatic reload when cart data changes (see [Syntax - Dynamic Content Reload](/syntax#dynamic-content-reload)) | The `_dodani.tpl` partial contains the `i:delivery-transports` and `i:delivery-payments` components documented above. The summary sidebar (`kosik_souhrn.tpl`) adapts its content based on the `checkout` variable — showing delivery costs and a "Continue to address" button on the delivery step. --- --- url: /editor.md --- # Editor Configuration ## PhpStorm ### Syntax Highlighting In *Settings*, under the *File Types* section, configure Twig syntax highlighting for *.tpl* file extensions. ![](img/phpstorm/syntax.png) ### Plugin A PhpStorm plugin is available that provides autocompletion for available variables and validates template syntax. After installation, no further configuration is needed — it works automatically for Simplia templates. ![](img/phpstorm/plugin.png) #### Plugin Installation In Settings, under the Plugins section, click the gear icon and select *Manage Plugin Repositories*. ![](img/phpstorm/plugin-1.png) Add the URL `https://phpstorm.simplia.cz` ![](img/phpstorm/plugin-2.png) Search for and install the `Symfony Support` and `Simplia templates` plugins. After installing the plugins, restart PhpStorm. ![](img/phpstorm/plugin-3.png) Autocompletion will be automatically available in templates of e-shops using the new syntax. If an update is released (for example, when a new object is added to templates), PhpStorm will offer it automatically. ![](img/phpstorm/plugin-4.png) ### Syntax Validation #### Configuring Inspections In the menu, select *Code* > *Inspect Code*: ![](img/phpstorm/inspection/code-inspect-1.png) Create a new profile by clicking the three-dot button *...*: ![](img/phpstorm/inspection/code-inspect-2.png) Download the Simplia.xml inspection definitions file and import it into PhpStorm — select the downloaded file and click *OK*. ![](img/phpstorm/inspection/code-inspect-3.png) #### Running Inspections In the menu, select *Code* > *Inspect Code*: ![](img/phpstorm/inspection/code-inspect-1.png) ![](img/phpstorm/inspection/code-inspect-4.png) ![](img/phpstorm/inspection/code-inspect-5.png) ## AI Agents and Machine Processing For AI agents and automated tools, the documentation site provides machine-readable resources: * **[llms.txt](/llms.txt)** / **[llms-full.txt](/llms-full.txt)** — the entire template documentation in a text format optimized for LLM processing. Suitable for loading the full documentation context at once. --- --- url: /events.md --- # Events When using jQuery, you can bind custom actions to various events. For example, after adding an item to the cart you can update the order total on the page, etc. ## Add to cart (order:itemadded) ```javascript $(document).on('order:itemadded', function(event, data) { // custom action }); ``` The event passes an object with the following content: ```javascript { item: { id: 1, // internal product id name: 'abc' // product name } } ``` ## Successful newsletter subscription (newsletter:success) ```javascript $(document).on('newsletter:success', function(event, data) { // custom action }); ``` The event passes an object with the following content: ```javascript { email: 'info@example.org' // registered email } ``` ## Failed newsletter subscription (newsletter:error) ```javascript $(document).on('newsletter:error', function(event, data) { // custom action }); ``` The event passes an object with the following content: ```javascript { email: 'info@example.org' } ``` ## Page change (listing:changed) Fires when the page changes during dynamic loading. ```javascript $('div.list').on('listing:changed', function(event, data) { // custom action }); ``` ## Dynamic content loaded (reload) Fires after content is loaded via the reload() function. ```javascript $(document).on('reload', 'div.list', function(event) { // custom action }); ``` ## Lazy menu loaded Fires after menu content is loaded in ``. ```javascript $(document).on('menu:loaded', function(event) { // custom action }); ``` --- --- url: /functions.md --- # Functions and filters This page documents custom Simplia functions. For standard Twig functions and filters, see the [official Twig documentation](https://twig.symfony.com/doc/2.x/). For an overview of custom Simplia filters, see the [Simplia filters](/twig#simplia-filters) section. ## uniqueEntities Processes an array of entities, removes duplicates, and optionally limits the result to a given count. A typical use case is displaying five products that are similar, in the same category, or on sale (depending on what is available). If a maximum count is defined, items are iterated only until the count is met. If two product listings are defined as sources and the count is already satisfied by the first, the second is never loaded from the database. ```twig {% for image in uniqueEntities([product.image, product.currentImages, product.imagesWithoutEnumValue], 5) %} {{ image.alt }} {% endfor %} ``` --- --- url: /getting-started.md --- # Getting Started This guide covers the minimum file set needed for a working Simplia template, explains how pages are rendered, and walks through adding a content section. Based on the **buran** production template. ## Minimal template A working template requires four files: ``` template/ ├── _base.tpl {# layout wrapper - wraps every page #} ├── config.json {# template configuration #} ├── index.tpl {# homepage content #} └── css/ └── base.scss {# main stylesheet (required entry point for SCSS) #} ``` ### Minimal \_base.tpl The layout wrapper includes a header, renders the current page template, and includes a footer. This is simplified from the buran `_base.tpl` -- feature-toggle conditionals have been stripped to show the core structure: ```twig {# _base.tpl - the outermost layout wrapper for every page #} {# Detect checkout pages: sekce_kosik = "cart section" #} {% set sekce_kosik = false %} {% if iUrl('Objednavka/Kosik') in URI %} {% set sekce_kosik = true %} {% endif %} {# Choose header: checkout pages get a simplified header #} {% if sekce_kosik and kos and sekce %} {% include '_header-checkout.tpl' %} {% else %} {% include '_header.tpl' %} {% endif %} {# Main content area - this.template resolves to the current page's .tpl file #}
      {% include this.template %}
      {# Footer: checkout pages get a simplified footer #} {% if sekce_kosik and kos %} {% include '_footer-checkout.tpl' %} {% else %} {% include '_footer.tpl' %} {% endif %} ``` Key points: * `{% include this.template %}` is the core mechanism -- the system sets `this.template` to the correct page file (e.g., `zbozi.tpl` for product detail) * `this.templateName` returns the current template name without extension (e.g., `zbozi`, `vypis`) * `sekce_kosik` (literally "cart section") detects checkout pages via `iUrl('Objednavka/Kosik')` -- the internal cart URL * `kos` is the cart object, `sekce` is the current checkout step name * `URI` is the current request URI ### Minimal config.json ```json { "responsive": true, "webfonts": [ "Manrope:500,600,700" ], "plugins": { "jquery": "3.4", "foundation": "6.4" }, "cartSteps": [ { "handles": ["content"] }, { "handles": ["delivery"], "query": "dodani" }, { "handles": ["address"], "query": "adresa" } ], "features": [ "syntax2019" ] } ``` * `responsive` -- enables viewport meta tag and responsive behavior * `webfonts` -- Google Fonts loaded automatically * `plugins` -- JS/CSS libraries injected before your code; `jquery` and `foundation` are the minimum for interactive templates * `cartSteps` -- defines the checkout flow; `handles` are step identifiers, `query` is the URL query parameter for that step (`dodani` = delivery, `adresa` = address) * `features` -- feature flags; `syntax2019` enables modern template syntax support For the full config.json reference, see [Configuration](/config). ### Minimal css/base.scss ```scss /* base.scss - required SCSS entry point */ /* All other .scss files should be @import-ed from here */ /* Optional: _settings.scss is injected before plugin styles, allowing you to override Foundation variables, colors, etc. */ body { font-family: 'Manrope', sans-serif; } ``` The build system compiles SCSS in this order: 1. `_settings.scss` (if present) -- injected before plugin styles 2. Plugin styles (Foundation, etc.) 3. `base.scss` -- your main stylesheet ## Page-to-file mapping Each shop page type maps to a specific template file. The system automatically selects the file based on the current URL and sets `this.template`. | Shop page | Template file | Czech name | Key variables | |---|---|---|---| | Homepage | `index.tpl` | Uvodní stránka | `repository`, `this.contentGrid()` | | Product detail | `zbozi.tpl` | Zboží | `product` | | Category listing | `vypis.tpl` | Výpis | `kategorie`, `this.nazev` | | Search results | `vyhledavani.tpl` | Vyhledávání | search query in URL | | Cart | `kosik.tpl` | Košík | `kos`, `cart`, `sekce` | | Order confirmation | `potvrzeni.tpl` | Potvrzení | `order` | | Order management | `objednavka.tpl` | Objednávka | `order` | | Registration | `registrace.tpl` | Registrace | form fields | | Static page | `stranka.tpl` | Stránka | `textPage` | | Article | `clanek.tpl` | Článek | `article` | | Store detail | `prodejna.tpl` | Prodejna | `store` | | Contact | `kontakty.tpl` | Kontakty | contact info | Czech identifier glossary: * `zbozi` = goods/product, `vypis` = listing, `kosik` = cart/basket * `potvrzeni` = confirmation, `objednavka` = order, `registrace` = registration * `stranka` = page, `clanek` = article, `prodejna` = store, `kontakty` = contacts * `this.nazev` = current page title (nazev = name) See [Object Types](/objects) for the full reference on `product`, `category`, `order`, and other objects. ## Page rendering flow When a visitor opens a URL, the rendering proceeds through four stages: ``` 1. URL resolution URL is resolved to a page type (e.g., /product/some-name -> product detail) The system binds variables: this.template = 'zbozi.tpl', product = 2. _base.tpl loads The layout wrapper begins rendering 3. Conditional header {% if sekce_kosik %} -> _header-checkout.tpl (simplified: logo + step indicators) {% else %} -> _header.tpl (full: logo, navigation, search, cart icon) 4. {% include this.template %} renders the page-specific file e.g., zbozi.tpl renders inside
      using the bound variables 5. Conditional footer {% if sekce_kosik %} -> _footer-checkout.tpl (simplified) {% else %} -> _footer.tpl (full footer with links, contact, social) ``` ### Concrete example: product detail ``` Visitor opens: /product/blue-sneakers 1. URL resolution -> page type: product detail 2. Variable binding: - this.template = 'zbozi.tpl' - this.templateName = 'zbozi' - product = {name: "Blue Sneakers", price: ..., image: ...} 3. _base.tpl renders: a. sekce_kosik = false (not a cart URL) -> includes _header.tpl b. {% include this.template %} -> renders zbozi.tpl zbozi.tpl can access {{ product.name }}, {{ product.price }}, etc. c. sekce_kosik = false -> includes _footer.tpl ``` ### Header selection logic `_base.tpl` chooses between two headers: * **`_header.tpl`** -- full header with logo, navigation menu, search bar, cart icon, user account links * **`_header-checkout.tpl`** -- simplified header for checkout pages (logo + checkout step indicators only) The condition is `{% if sekce_kosik and kos and sekce %}` where: * `sekce_kosik` = true when the URL contains the cart path * `kos` = the cart object (truthy when a cart exists) * `sekce` = current checkout step name (`'udaje'`, `'dodani'`); null/empty on the initial cart step (equivalent to `'kosik'`) ### Content area The `
      ` tag wraps the page content. The `data-template` attribute is set to `this.templateName` for CSS/JS targeting: ```html
      ``` This allows page-specific styling: `main[data-template="zbozi"] { ... }` ## Adding a content section Content grid allows shop admins to configure content blocks through the admin panel without template code changes. Here is a step-by-step example adding a USP (Unique Selling Proposition) bar with repeatable image + text items. ### Step 1: Create the XML schema Create `content-grid/header-usp.xml`: ```xml Header - USP true part ``` Key attributes: * `multiple="true"` on `` -- admins can add zero or more items, and reorder them * `code="item"` -- how you access this row in Twig: `this.contentGrid('header-usp').item` * `type="image"` -- image upload field; `type="html_simple"` -- basic rich text editor * `width` -- percentage width in the admin panel layout (visual only, not output) ### Step 2: Reference in template Use `this.contentGrid('section-name')` where the name matches the XML filename without extension: ```twig {# In _header.tpl or wherever you want the USP bar #} {% if this.contentGrid('header-usp') %}
      {% for item in this.contentGrid('header-usp').item %}
      {# Render the uploaded icon image #} {# Render the rich text content #} {{ item.text }}
      {% endfor %}
      {% endif %} ``` Pattern notes: * Always wrap in `{% if this.contentGrid('...') %}` -- returns null if no content has been configured * `this.contentGrid('header-usp').item` returns an array because of `multiple="true"` * `i:img` is the platform image tag with responsive format support (see [Syntax](/syntax)) ### Step 3: Admin configuration Once the XML file is deployed and the template references it, the content grid section automatically appears in the admin panel under template settings. Shop administrators fill in the content through a visual editor -- no further code is needed. For the full content grid reference including all content types (string, html, image, color, url, category, product, product\_list, select, article, text\_page), see [Content Grid](/content-grid). ## Next steps * [Template file structure](/files) -- directory layout and naming conventions * [Configuration](/config) -- config.json options in detail * [Syntax](/syntax) -- Twital custom tags (`i:img`, `i:list-container`, `if:class`, etc.) * [Object Types](/objects) -- `product`, `category`, `cart`, and other object references * [Examples](/examples) -- complete code recipes from production templates --- --- url: /global.md --- # Global elements This page describes system elements that are automatically injected into templates. Their HTML structure and behavior are controlled by the system — in the template you can only affect their appearance via CSS. Global variables available in all templates (`cart`, `customer`, `shop`, `repository`, etc.) can be found in the [object reference](/objects). ## Announcements In the admin panel under Texts / Announcements, you can create informational announcements that are displayed above the website. Announcements have a defined validity period, display style, and an optional close button. The styles are predefined and announcements should work without any template modifications. To test announcements while working on a template, you can trigger a placeholder announcement by adding a URL parameter: * `?_previewAnnouncement=info` * `?_previewAnnouncement=warning` * `?_previewAnnouncement=important` The HTML is fixed and generated automatically, but you can adjust the appearance via CSS in the template. When modifying the appearance, only cosmetic tweaks should be made — matching the announcement color shade, font size, and similar. Larger changes should always be made centrally in the system. Likewise, it is not possible to attach custom JavaScript to announcements in the template. In the admin panel, announcements currently support switching between these classes: * `global-announcement--info` * `global-announcement--warning` * `global-announcement--important` ```html
      Announcement text with a link and highlighting
      ``` --- --- url: /html.md --- # HTML Elements ## i:img (images) Renders an image for a product, category, article, etc. Not used for static template images (icons, etc.). Handles thumbnail URL generation, retina display support, and automatic image alt text. When multiple dimensions are specified, a srcset is generated that can be used in combination with the HTML5 *sizes* attribute. If the *sizes* attribute is omitted, *width* and *height* attributes are set based on the actual resulting image dimensions. If the **alt** attribute is not specified, it is automatically populated from the image description or the product/variant/... name. The output differs depending on the device (retina/non-retina) and load order (first/subsequent). **Retina images may overflow -- you must handle this in CSS**, for example with *max-width:100%;max-height:100%*. ```twig ``` **Attributes** | Attribute | Required | Description | |---|---|---| | **src** | **yes** | Twig object | | **format** | **yes** | image dimensions | | sizes | *no* | same as HTML5 sizes -- required when multiple dimensions are specified | | watermark | *no* | image will have a watermark | | fill | *no* | pad the image with white borders | | vector | *no* | use PNG/SVG | | transparent | *no* | image has transparency | | quality | *no* | low/normal/high/lossless (default: normal) | | lazyload | *no* | *src* and *srcset* as data attributes | | class | --- | CSS class | ### Image Zoom Wrap an image in a link with **@** as the href -- it will be replaced with the appropriate URL. The link may contain **only one image**. If you need a conditional, use two separate links. ```twig ``` ## i:img360 -- 360 Product View ```twig ``` **Attributes** | Attribute | Required | Description | |---|---|---| | **product** | **yes** | Product object | | format | *no* | image dimensions (same as i:img). Default: \[500x500, 1000x1000, 1500x1500, 2000x2000] | | sizes | *no* | responsiveness for images (same as i:img) -- required when multiple dimensions are specified | | zoom | *no* | integer zoom value (default: 3) | | no-steps | *no* | hide forward/backward step buttons | | no-fullscreen | *no* | hide fullscreen button | | no-play | *no* | hide play/stop button | | hide-logo | *no* | hide 360 view logo | | bottom-circle | *no* | show bottom 360 circle | | lazyload | *no* | load library and images only when the element enters the viewport | | lazyload="click" | *no* | load library and images only when the element is clicked | | hidden | *no* | element is hidden (display:none) and activated via JS (used for fancybox) | | autoplay | *no* | | To display inside a FancyBox, add a thumbnail image and a hidden player. The specific class name does not matter -- what matters is pairing the thumbnail with the player. ```twig