Skip to content

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
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://tpldoc.simplia.cz/xsd/content-grid.xsd">
    <config>
        <name>Obsahová mřížka</name> <!-- name for orientation in the admin panel - displayed on the add button -->
        <priority>1</priority> <!-- priority for ordering in the admin panel -->
        <translations>false</translations> <!-- when translations are enabled, content is created separately for each language in the admin panel -->
        <mode>full_page</mode> <!-- "full_page" offers content-grid in the page type dropdown, "part" displays all content grids as buttons -->
        <supported-context type="article"/> <!-- which contexts the content is supported for -->
        <supported-context type="text_page"/>
    </config>
    <grid>
        <row label="Hlavička" code="header"> <!-- "label" sets the heading in the admin panel, "code" defines how the content is referenced in the template -->
            <content type="string" label="Nadpis" code="headline" width="20"/> <!-- optionally, width can be defined in percent -->
            <content type="image" label="Ikonka nadpisu" code="image"/>
        </row>
        <row label="Obsah" code="content">
            <content type="html" label="Text" code="text"/>
            <content type="image" label="Nadpis3" code="image2"/>
            <cell label="vyberte kategorii" code="image2">
                <row code="text">
                    <content type="html" label="Text" code="text"/>
                </row>
                <row code="image2">
                    <content type="image" label="Nadpis3" code="image2" multiple="true" /> <!-- content with multiple enabled can be reordered in the admin panel -->
                </row>
            </cell>
        </row>
        <row label="Obsah2" code="content2" multiple="true"> <!-- the "multiple" attribute allows adding multiple blocks (or none) - in the template "content2" is then available as an array -->
            <content type="html" label="Text" code="text"/>
            <content type="image" label="Nadpis3" code="image2"/>
            <content type="category" label="kategorie" code="category" multiple="true"/>
            <content type="select" label="Test selectu" code="selection" width="100">
                <choice code="first" label="První"/>
                <choice code="second" label="Druhý"/>
                <choice code="third" label="Třetí"/>
            </content>
        </row>
    </grid>
</schema>

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.

twig
{% if product.contentGrid('product-cta') %}
    <h3>{{ product.contentGrid('product-cta').header.headline }}</h3>
    {% for content in product.contentGrid('product-cta').content2 %}
        {{ content.text }}
    {% endfor %}
{% endif %}
twig
{% if this.contentGrid('banner-cta') %}
    <h3>{{ this.contentGrid('banner-cta').header.headline }}</h3>
    {% for content in this.contentGrid('banner-cta').content2 %}
        {{ content.text }}
    {% endfor %}
{% endif %}

Content Type Reference

TypeDescriptionTemplate Access
stringSingle-line plain text input{{ row.code }} -- renders as plain string
htmlRich text editor (full WYSIWYG){{ row.code }} -- renders as raw HTML
html_simpleSimplified rich text editor (basic formatting only -- bold, italic, links){{ row.code }} -- renders as raw HTML
imageImage upload (PNG, JPG, SVG, ...)<i:img src="row.code" format="300x300" />
colorColor picker (hex code){{ row.code }} -- renders as #rrggbb string
urlURL with link text and title{{ row.code.url }}, {{ row.code.label }}, {{ row.code.title }}
categoryCategory selector from catalog treeReturns a category object with .url, .name, etc.
productSingle product selectorReturns a product object
product_listProduct selection by rules (category, attributes, etc.)Returns a collection for i:list-container collection attribute
selectDropdown with predefined <choice> child elements{{ row.code }} -- renders the selected choice code value
articleArticle selectorReturns an article object with .url, .name, etc.
text_pageText page selectorReturns 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.

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
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://tpldoc.simplia.cz/xsd/content-grid.xsd">
    <config>
        <name>Patička - USP</name> <!-- Footer - USP icons -->
        <translations>true</translations>
        <mode>part</mode>
        <supported-context type="global"/>
    </config>

    <grid>
        <row code="item" label="USP" multiple="true">
            <content type="image" label="Obrázek" code="image" width="30" />
            <content type="html_simple" label="Text" code="text" width="70" />
        </row>
    </grid>
</schema>

Template (_footer-usp.tpl) -- the actual buran template renders USP items in a flex row with SVG icon support:

twig
{# _footer-usp.tpl -- loaded from _footer.tpl via {% include '_footer-usp.tpl' %} #}
{% set items = this.contentGrid('footer-usp').item %}
<div class="usp-bar">
    <div class="usp-bar__inner">
        {% for item in items %}
            <div class="usp-item">
                {# vector + transparent flags enable SVG rendering without background #}
                <i:img src="item.image" format="30x30" loading="lazy" vector transparent />
                <div>{{ item.text }}</div>
            </div>
        {% endfor %}
    </div>
</div>

The parent _footer.tpl conditionally includes this partial:

twig
{% if this.contentGrid('footer-usp').item %}
    {% include '_footer-usp.tpl' %}
{% endif %}

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
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://tpldoc.simplia.cz/xsd/content-grid.xsd">
    <config>
        <name>Patička - Sloupečky s odkazy</name> <!-- Footer - Link columns -->
        <translations>true</translations>
        <mode>part</mode>
        <supported-context type="global"/>
    </config>

    <grid>
        <row code="item" label="Sloupeček" multiple="true"> <!-- Repeatable column -->
            <content type="string" label="Nadpis" code="headline" width="100" /> <!-- Column heading -->
            <content type="url" multiple="true" label="Odkaz" code="link" width="100" /> <!-- Repeatable link -->
        </row>
    </grid>
</schema>

Template (_footer.tpl excerpt) -- the buran footer limits to 3 columns and renders links as a <ul> list with collapsible headings on mobile:

twig
{# _footer.tpl -- iterate footer columns (limited to first 3) #}
{% for item in this.contentGrid('footer-column-links').item %}
    {% if loop.index <= 3 %}
        <div class="footer-box">
            <h4 class="footer-box__headline">
                {{ item.headline }}
                {# Chevron icon for mobile accordion toggle #}
                <svg class="footer-box__headline-angle icon">
                    <use xlink:href="{{ images('symbol-defs.svg#bi-chevron-down') }}"></use>
                </svg>
            </h4>
            <div class="footer-box__info">
                <ul>
                    {# Inner loop: each link has .url and .label from the url type #}
                    {% for link in item.link %}
                        <li><a href="{{ link.url }}">{{ link.label }}</a></li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    {% endif %}
{% endfor %}

3. Complex: Homepage banner with products

A complex schema using nested <cell> 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.

Schema (content-grid/homepage-banner-products.xml):

xml
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://tpldoc.simplia.cz/xsd/content-grid.xsd">
    <config>
        <name>Homepage - Banner s produkty</name> <!-- Homepage - Banner with products -->
        <translations>true</translations>
        <mode>part</mode>
        <supported-context type="global"/>
    </config>

    <grid>
        <row code="item" label="Banner" multiple="true">
            <cell code="left" width="30" label="Info vlevo"> <!-- Left panel in admin -->
                <content type="color" label="Barva pozadí" code="color" width="100" />
                <content type="image" label="Obrázek" code="image" width="100" />
            </cell>

            <cell code="right" width="70" label="Info vpravo"> <!-- Right panel in admin -->
                <content type="string" label="Nadpisek" code="text" width="100" />
                <content type="string" label="Nadpis" code="headline" width="100" />
                <content type="html_simple" label="Popisek" code="description" width="100" />
                <content type="url" label="Tlačítko" code="button" width="100" />
                <content type="product_list" label="Produkty" code="products" width="100" />
            </cell>
        </row>
    </grid>
</schema>

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:

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 %}
        <div class="banner-products" if:style="item.left.color ? '--banner-bg-color: ' ~ item.left.color">
            {# Left side: promotional image #}
            <div class="banner-products__image">
                <i:img src="item.left.image" format="600x600" loading="lazy" alt="{{ item.right.headline }}" />
            </div>

            {# Right side: text content with CTA button #}
            <div class="banner-products__text">
                <div>
                    <span>{{ item.right.text }}</span>
                    <strong>{{ item.right.headline }}</strong>
                    {% if item.right.description %}
                        <div>{{ item.right.description }}</div>
                    {% endif %}
                    <a href="{{ item.right.button.url }}" class="button">
                        {{ item.right.button.label }}
                    </a>
                </div>
            </div>

            {# Product carousel using the "simple" list-item variant from _listing.tpl #}
            <i:list-container type="product" name="simple" collection="{{ item.right.products }}" hide-empty
                              class="swiper-container" data-navigation="simple-slider-{{ loop.index }}">
                <div data-role="list" class="swiper-wrapper"></div>
            </i:list-container>
        </div>
    {% endfor %}
{% endif %}