Display Visuals in Product Grid

By default, Tweakwise Visuals serve a single image URL as defined in your data feed. Tweakwise does not provide an out-of-the-box convention to display text content tiles. This can be solved using a custom implementation using the Tweakwise JS render hook and your own frontend logic.

Implementation

To change the appearance of the tile, use the twn.product.visible hook.

Step 1: Prepare your data in Tweakwise

Add attributes to your visuals in the Tweakwise app (or your feed).
For example, add an attribute visual_title and visual_subtitle to your visual.

Consider adding an extra field for an image on different screensizes (though it's recommended to always use 1 squere image that can be always be cropped in an acceptable way).

Example: add your data to the item

<item>
  <id>guided-selling-style</id>
  <name>Guided Selling - Style</name>
  <image><![CDATA[https://cdn.tweakwise.dev/content/shop/findyourperfectfit.png]]></image>
  ...
  <attributes>
    ...
    <attribute>
      <name>visual_title</name>
      <value><![CDATA[Find your perfect fit]]></value>
    </attribute>
    <attribute>
      <name>visual_subtitle</name>
      <value><![CDATA[Use the guided selling]]></value>
    </attribute>
    ...
  </attributes>
</item>

Next, extend the API reponse with the attribute to expose the attribute so you can use it in Tweakwise JS.

Step 2: Use the visibility hook

Add the following logic to your tweakwiseListerPage function call. This listens for the item visibility event, identifies visual items, and appends the elements to the tile:

tweakwiseListerPage({
  // ...
  on: {
  
    "twn.product.visible": function (event) {
      
      const item = event.data;
  
      if (item.type === "visual"){
        tw__visual(event.targetElement, item)
      }
      
    }

  }
})

.tw__visual-container{
    padding: 30px;
    display: flex;
    flex-direction: column;
    height: 100%;
    justify-content: space-between;
}

.tw__visual-title {
    position: relative;
    z-index: 100;
    color:#fff;
    text-decoration: none;
    font-size: 2rem;
    border: none;
    font-weight: bold;
}

.tw__visual-button {
    position: relative;
    z-index: 100;
    display: inline-flex;
    background: #fff000;
    color: #222;
    border-radius: 20px;
    padding: 8px 16px;
    font-weight: bold;
    text-decoration: none;
    border: none;
    text-align: center;
    align-items: center;
    justify-content: center;
}

export function tw__visual(element, item){
  if (item.type !== "visual") return;

  const titleAttribute = item.attributes.find((o) => o.name == "visual_title");
  const buttonAttribute = item.attributes.find((o) => o.name == "visual_button");
  const themeAttribute = item.attributes.find((o) => o.name == "theme");

  const target = element.querySelector(".twn-content-tile__link");
  const container = createElement("div", "tw__visual-container");

  if(titleAttribute && titleAttribute.values){
      const visual_title = titleAttribute.values[0];
      var title = createElement("div", "tw__visual-title", visual_title);
      container.append(title);
  }
  if(buttonAttribute && buttonAttribute.values){
      const visual_button = buttonAttribute.values[0];
      var link = createElement("a", "tw__visual-button", visual_button);
      container.append(link);
  }
  if(themeAttribute && themeAttribute.values){
    container.classList.add('has-theme-' + themeAttribute.values[0])
  }

  target.append(container);
}