Implementing Flexible Sized Grid Items in Magento 2 with Hyvä

This guide explains how to implement Flexible Sized Grid Items in a Magento 2 store running Hyvä Themes. By following these steps, products and visuals in your category and search result pages will respect the colspan and rowspan values configured in the Tweakwise Merchandising Builder.

Note: This article is an implementation suggestion based on the Magento2TweakwiseHyva module. The actual implementation depends on the specifications and requirements of your platform. Use this as a starting point and adapt it to your specific project setup.


Prerequisites

Before you begin, make sure you have:


How it works

When Flexible Sized Grid Items are enabled in the Merchandising Builder, the Tweakwise API returns visualproperties for each item in the response:

<item>
  <itemno>100012674</itemno>
  <type>product</type>
  <title>Example Product</title>
  <visualproperties>
    <colspan>2</colspan>
    <rowspan>1</rowspan>
  </visualproperties>
</item>

The Magento 2 Tweakwise module parses these values and attaches them to the product object in the collection. Your frontend template is then responsible for translating colspan and rowspan into CSS Grid properties (grid-column: span X and grid-row: span Y).


Implementation

The implementation consists of two parts:

  1. Layout XML — Override the product list template for category and search pages.
  2. Product list template — A custom .phtml template that reads visual properties and applies CSS Grid spanning.

Step 1: Create the layout XML overrides

Create or modify the layout XML files in your theme or custom module to point the product list blocks to your custom template.

Category pages — add to view/frontend/layout/hyva_catalog_category_view.xml:

<referenceBlock name="category.products.list">
    <action method="setTemplate">
        <argument name="template" xsi:type="string">Tweakwise_TweakwiseHyva::product/list.phtml</argument>
    </action>
</referenceBlock>

Search results page — add to view/frontend/layout/hyva_catalogsearch_result_index.xml:

<referenceBlock name="search_result_list">
    <action method="setTemplate">
        <argument name="template" xsi:type="string">Tweakwise_TweakwiseHyva::product/list.phtml</argument>
    </action>
</referenceBlock>

Step 2: Create the product list template

Create the template file at view/frontend/templates/product/list.phtml. This template is based on the default Hyvä product list template but adds visual property support for grid spanning. The example below is taken from the Magento2TweakwiseHyva module.

<?php
/**
 * Hyvä Themes - https://hyva.io
 * Copyright © Hyvä Themes 2020-present. All rights reserved.
 * This product is licensed per Magento install
 * See https://hyva.io/license
 */

use Hyva\Theme\Model\ViewModelRegistry;
use Hyva\Theme\ViewModel\CurrentCategory;
use Hyva\Theme\ViewModel\ProductListItem;
use Hyva\Theme\ViewModel\ProductPage;
use Magento\Catalog\Block\Product\ListProduct;
use Magento\Framework\Escaper;
use Tweakwise\Magento2Tweakwise\Model\Client\Type\ItemType;

/** @var ListProduct $block */
/** @var Escaper $escaper */
/** @var ViewModelRegistry $viewModels */
/** @var ProductPage $productViewModel */
/** @var CurrentCategory $currentCategoryViewModel */

$productViewModel = $viewModels->require(ProductPage::class);
$productListItemViewModel = $viewModels->require(ProductListItem::class);
$currentCategoryViewModel = $viewModels->require(CurrentCategory::class);

$eagerLoadImagesCount = (int) ($block->getData('eager_load_images_count') ?? 3);
$productCollection = $block->getLoadedProductCollection();

?>
<?php if (!$productCollection->count()): ?>
<div class="message info empty">
    <div>
        <?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')) ?>
    </div>
</div>
<?php else: ?>
<section class="py-8" id="product-list" aria-label="<?= $escaper->escapeHtmlAttr(__('Product list')) ?>" tabindex="-1">
    <?= $block->getToolbarHtml() ?>
    <?= $block->getAdditionalHtml() ?>
    <?php
    if ($block->getMode() == 'grid') {
        $viewMode = 'grid';
        $imageDisplayArea = 'category_page_grid';
        $showDescription = false;
        $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
    } else {
        $viewMode = 'list';
        $imageDisplayArea = 'category_page_list';
        $showDescription = true;
        $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW;
    }
    ?>
    <div class="products wrapper mode-<?= /* @noEscape */$viewMode ?> products-<?= /* @noEscape */$viewMode ?>">
        <ul
            role="list"
            class="mx-auto pt-4 pb-12 grid gap-4 grid-cols-1 <?= /* @noEscape */$viewMode === 'grid'
                ? 'sm:grid-cols-2 xl:grid-cols-3'
                : '' ?>"
        >
            <?php
            /** @var \Magento\Catalog\Model\Product $product */
            foreach (array_values($productCollection->getItems()) as $i => $product):
                if ($i < $eagerLoadImagesCount) {
                    $product->setData('image_custom_attributes', ['loading' => 'eager', 'fetchpriority' => 'high']);
                }

                $colspan = (int) ($product->getData(ItemType::COLSPAN) ?? 0);
                $rowspan = (int) ($product->getData(ItemType::ROWSPAN) ?? 0);

                $visualProperties = $product->getData(ItemType::VISUAL_PROPERTIES);
                if (is_array($visualProperties)) {
                    $colspan = max($colspan, (int) ($visualProperties[ItemType::COLSPAN] ?? 0));
                    $rowspan = max($rowspan, (int) ($visualProperties[ItemType::ROWSPAN] ?? 0));
                }

                if (method_exists($product, 'getColspan')) {
                    $colspan = max($colspan, (int) $product->getColspan());
                }
                if (method_exists($product, 'getRowspan')) {
                    $rowspan = max($rowspan, (int) $product->getRowspan());
                }

                $gridStyle = '';
                if ($colspan > 1) {
                    $gridStyle .= 'grid-column: span ' . $colspan . ';';
                }
                if ($rowspan > 1) {
                    $gridStyle .= 'grid-row: span ' . $rowspan . ';';
                }
            ?>
                <li<?= $gridStyle ? ' style="' . $escaper->escapeHtmlAttr($gridStyle) . '"' : '' ?>>
                    <?= $productListItemViewModel->getItemHtml(
                        $product,
                        $block,
                        $viewMode,
                        $templateType,
                        $imageDisplayArea,
                        $showDescription
                    ); ?>
                </li>
            <?php endforeach; ?>
        </ul>
    </div>
    <?= $block->getChildBlock('toolbar')->setIsBottom(true)->toHtml() ?>
</section>
<?php endif; ?>

How the template reads visual properties

The Tweakwise module stores visual properties on each product in the collection. The values can come from multiple sources, so the template checks all of them and takes the maximum value:

  1. Direct data attributes$product->getData(ItemType::COLSPAN) and $product->getData(ItemType::ROWSPAN)
  2. Nested visual properties array$product->getData(ItemType::VISUAL_PROPERTIES) which contains colspan and rowspan keys
  3. Getter methods$product->getColspan() and $product->getRowspan() if available on the object

The resulting colspan and rowspan values are then translated to inline CSS Grid styles on each <li> element:

<li style="grid-column: span 2; grid-row: span 1;">
    <!-- product card -->
</li>

Reference