jelonyx
§ / Shopify · Sections · Section 06

Collection feature grid
section.

This collection feature grid drops onto any Shopify homepage, landing page, or category index. You add a collection per block in the theme editor, optionally override the image and title, and choose between three card layouts: image above text, text over image, or image beside text on desktop. Columns, gap, aspect ratio, and product-count display are all configurable. It works without any third-party app.

TypeSection
Filesections/jlx-collection-grid.liquid
Schema settings12 section + 5 per card
CostFree
Compatible with:Dawn 12+HorizonSenseCraftRefreshMost OS 2.0 themes

The code

One file. Paste the whole thing into a new section.

sections/jlx-collection-grid.liquid
Liquid · CSS · Schema
{% comment %}
  Jelonyx · Collection Feature Grid Section
  Compatible with Dawn 12+, Horizon, and most OS 2.0 themes
  Source: jelonyx.com/shopify/sections/collection-grid
{% endcomment %}

{%- liquid
  assign cols_d = section.settings.columns_desktop | default: 3
  assign cols_m = section.settings.columns_mobile | default: 1
  assign card_style = section.settings.card_style | default: 'image_above'
  assign ratio = section.settings.aspect_ratio | default: 'square'
  assign gap = section.settings.gap | default: 20
  assign pad_t = section.settings.padding_top | default: 60
  assign pad_b = section.settings.padding_bottom | default: 60
-%}

{%- if section.blocks.size > 0 -%}

<section
  id="jlx-cg-{{ section.id }}"
  class="jlx-cg jlx-cg--{{ card_style }} jlx-cg--ratio-{{ ratio }}"
  style="--jlx-cg-cols-d: {{ cols_d }}; --jlx-cg-cols-m: {{ cols_m }}; --jlx-cg-gap: {{ gap }}px; --jlx-cg-pt: {{ pad_t }}px; --jlx-cg-pb: {{ pad_b }}px;"
>
  <div class="jlx-cg__inner">

    {%- if section.settings.heading != blank -%}
      <h2 class="jlx-cg__heading">{{ section.settings.heading }}</h2>
    {%- endif -%}

    {%- if section.settings.subheading != blank -%}
      <p class="jlx-cg__subheading">{{ section.settings.subheading }}</p>
    {%- endif -%}

    <div class="jlx-cg__grid">
      {%- for block in section.blocks -%}
        {%- assign coll = block.settings.collection -%}
        {%- if coll != blank -%}

          {%- if block.settings.image_override != blank -%}
            {%- assign card_image = block.settings.image_override -%}
          {%- elsif coll.image != blank -%}
            {%- assign card_image = coll.image -%}
          {%- elsif coll.products.first.featured_image != blank -%}
            {%- assign card_image = coll.products.first.featured_image -%}
          {%- else -%}
            {%- assign card_image = blank -%}
          {%- endif -%}

          {%- if block.settings.title_override != blank -%}
            {%- assign card_title = block.settings.title_override -%}
          {%- else -%}
            {%- assign card_title = coll.title -%}
          {%- endif -%}

          <a
            href="{{ coll.url }}"
            class="jlx-cg__card"
            {{ block.shopify_attributes }}
          >
            <div class="jlx-cg__media">
              {%- if card_image != blank -%}
                <img
                  src="{{ card_image | image_url: width: 800 }}"
                  srcset="{{ card_image | image_url: width: 400 }} 400w, {{ card_image | image_url: width: 600 }} 600w, {{ card_image | image_url: width: 800 }} 800w, {{ card_image | image_url: width: 1200 }} 1200w"
                  sizes="(min-width: 990px) calc(100vw / {{ cols_d }}), (min-width: 750px) 50vw, calc(100vw / {{ cols_m }})"
                  width="{{ card_image.width }}"
                  height="{{ card_image.height }}"
                  alt="{{ card_image.alt | default: card_title | escape }}"
                  loading="lazy"
                  class="jlx-cg__img"
                />
              {%- else -%}
                <div class="jlx-cg__img jlx-cg__img--empty" aria-hidden="true"></div>
              {%- endif -%}
              {%- if card_style == 'image_overlay' -%}
                <div class="jlx-cg__overlay" aria-hidden="true"></div>
              {%- endif -%}
            </div>

            <div class="jlx-cg__body">
              <p class="jlx-cg__title">{{ card_title }}</p>
              {%- if section.settings.show_subtitle and block.settings.subtitle != blank -%}
                <p class="jlx-cg__subtitle">{{ block.settings.subtitle }}</p>
              {%- endif -%}
              {%- if section.settings.show_count -%}
                <p class="jlx-cg__count">{{ coll.products_count }} products</p>
              {%- endif -%}
              {%- if block.settings.cta_label != blank -%}
                <span class="jlx-cg__cta">{{ block.settings.cta_label }} <span aria-hidden="true">→</span></span>
              {%- endif -%}
            </div>
          </a>

        {%- endif -%}
      {%- endfor -%}
    </div>

  </div>
</section>

<style>
  #jlx-cg-{{ section.id }} {
    padding: var(--jlx-cg-pt, 60px) 20px var(--jlx-cg-pb, 60px);
    background: var(--color-background, #fff);
  }
  #jlx-cg-{{ section.id }} .jlx-cg__inner {
    max-width: 1280px;
    margin: 0 auto;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__heading {
    font-size: clamp(22px, 3.5vw, 34px);
    font-weight: 700;
    line-height: 1.2;
    color: var(--color-foreground, #1a1a1a);
    margin: 0 0 10px;
    text-align: center;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__subheading {
    font-size: 15px;
    line-height: 1.6;
    color: var(--color-foreground-secondary, #6b6b6b);
    text-align: center;
    margin: 0 0 36px;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__grid {
    display: grid;
    grid-template-columns: repeat(var(--jlx-cg-cols-m, 1), 1fr);
    gap: var(--jlx-cg-gap, 20px);
  }
  @media (min-width: 750px) {
    #jlx-cg-{{ section.id }} .jlx-cg__grid {
      grid-template-columns: repeat(2, 1fr);
    }
  }
  @media (min-width: 990px) {
    #jlx-cg-{{ section.id }} .jlx-cg__grid {
      grid-template-columns: repeat(var(--jlx-cg-cols-d, 3), 1fr);
    }
  }
  #jlx-cg-{{ section.id }} .jlx-cg__card {
    display: block;
    color: var(--color-foreground, #1a1a1a);
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 10px;
    overflow: hidden;
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
    position: relative;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__card:hover {
    transform: translateY(-2px);
    border-color: var(--color-border, rgba(0,0,0,0.12));
    box-shadow: 0 8px 24px rgba(0,0,0,0.06);
  }
  #jlx-cg-{{ section.id }} .jlx-cg__media {
    position: relative;
    overflow: hidden;
    background: var(--color-background-2, rgba(0,0,0,0.03));
  }
  #jlx-cg-{{ section.id }} .jlx-cg__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.4s ease;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__img--empty {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--color-background-2, rgba(0,0,0,0.05));
  }
  #jlx-cg-{{ section.id }} .jlx-cg__card:hover .jlx-cg__img {
    transform: scale(1.04);
  }
  #jlx-cg-{{ section.id }}.jlx-cg--ratio-square .jlx-cg__media { aspect-ratio: 1 / 1; }
  #jlx-cg-{{ section.id }}.jlx-cg--ratio-portrait .jlx-cg__media { aspect-ratio: 3 / 4; }
  #jlx-cg-{{ section.id }}.jlx-cg--ratio-landscape .jlx-cg__media { aspect-ratio: 4 / 3; }
  #jlx-cg-{{ section.id }}.jlx-cg--ratio-auto .jlx-cg__media { aspect-ratio: auto; }

  #jlx-cg-{{ section.id }} .jlx-cg__body {
    padding: 14px 4px 4px;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__title {
    font-size: 16px;
    font-weight: 600;
    line-height: 1.3;
    margin: 0;
    color: var(--color-foreground, #1a1a1a);
  }
  #jlx-cg-{{ section.id }} .jlx-cg__subtitle {
    font-size: 13px;
    line-height: 1.5;
    margin: 4px 0 0;
    color: var(--color-foreground-secondary, #6b6b6b);
  }
  #jlx-cg-{{ section.id }} .jlx-cg__count {
    font-size: 12px;
    font-weight: 500;
    margin: 6px 0 0;
    color: var(--color-foreground-secondary, #888);
    letter-spacing: 0.02em;
  }
  #jlx-cg-{{ section.id }} .jlx-cg__cta {
    display: inline-block;
    margin-top: 10px;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-foreground, #1a1a1a);
    border-bottom: 1px solid currentColor;
    padding-bottom: 1px;
  }

  /* image_overlay variant */
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__card {
    border: none;
  }
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__media {
    border-radius: 10px;
  }
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.55) 100%);
    pointer-events: none;
  }
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__body {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 18px;
    z-index: 2;
    color: #fff;
  }
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__title,
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__subtitle,
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__count,
  #jlx-cg-{{ section.id }}.jlx-cg--image_overlay .jlx-cg__cta {
    color: #fff;
  }

  /* image_left variant */
  @media (min-width: 750px) {
    #jlx-cg-{{ section.id }}.jlx-cg--image_left .jlx-cg__card {
      display: grid;
      grid-template-columns: 40% 1fr;
      align-items: center;
    }
    #jlx-cg-{{ section.id }}.jlx-cg--image_left .jlx-cg__body {
      padding: 18px 18px 18px 20px;
    }
  }
</style>

{%- else -%}
  {%- if request.design_mode -%}
    <div style="padding:60px 20px;text-align:center;border:2px dashed rgba(0,0,0,0.12);border-radius:8px;">
      <p style="color:#888;font-size:14px;margin:0;">Add collection blocks using the section settings to build your collection grid.</p>
    </div>
  {%- endif -%}
{%- endif -%}

{% schema %}
{
  "name": "Collection feature grid",
  "tag": "section",
  "class": "section",
  "max_blocks": 12,
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "placeholder": "Shop by category"
    },
    {
      "type": "text",
      "id": "subheading",
      "label": "Subheading"
    },
    { "type": "header", "content": "Layout" },
    {
      "type": "select",
      "id": "columns_desktop",
      "label": "Columns on desktop",
      "options": [
        { "value": "2", "label": "2" },
        { "value": "3", "label": "3" },
        { "value": "4", "label": "4" },
        { "value": "5", "label": "5" }
      ],
      "default": "3"
    },
    {
      "type": "select",
      "id": "columns_mobile",
      "label": "Columns on mobile",
      "options": [
        { "value": "1", "label": "1" },
        { "value": "2", "label": "2" }
      ],
      "default": "1"
    },
    {
      "type": "select",
      "id": "card_style",
      "label": "Card style",
      "options": [
        { "value": "image_above", "label": "Image above text" },
        { "value": "image_overlay", "label": "Text over image" },
        { "value": "image_left", "label": "Image beside text" }
      ],
      "default": "image_above"
    },
    {
      "type": "select",
      "id": "aspect_ratio",
      "label": "Image aspect ratio",
      "options": [
        { "value": "square", "label": "Square (1:1)" },
        { "value": "portrait", "label": "Portrait (3:4)" },
        { "value": "landscape", "label": "Landscape (4:3)" },
        { "value": "auto", "label": "Original" }
      ],
      "default": "square"
    },
    {
      "type": "range",
      "id": "gap",
      "label": "Gap between cards",
      "min": 0,
      "max": 60,
      "step": 4,
      "unit": "px",
      "default": 20
    },
    { "type": "header", "content": "Card content" },
    {
      "type": "checkbox",
      "id": "show_count",
      "label": "Show product count",
      "default": true
    },
    {
      "type": "checkbox",
      "id": "show_subtitle",
      "label": "Show subtitle",
      "default": true
    },
    { "type": "header", "content": "Section padding" },
    {
      "type": "range",
      "id": "padding_top",
      "label": "Padding top",
      "min": 0,
      "max": 160,
      "step": 4,
      "unit": "px",
      "default": 60
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "label": "Padding bottom",
      "min": 0,
      "max": 160,
      "step": 4,
      "unit": "px",
      "default": 60
    }
  ],
  "blocks": [
    {
      "type": "card",
      "name": "Collection card",
      "settings": [
        {
          "type": "collection",
          "id": "collection",
          "label": "Collection"
        },
        {
          "type": "image_picker",
          "id": "image_override",
          "label": "Image override",
          "info": "Optional. Falls back to the collection image, then the first product image."
        },
        {
          "type": "text",
          "id": "title_override",
          "label": "Title override",
          "info": "Optional. Falls back to the collection title."
        },
        {
          "type": "text",
          "id": "subtitle",
          "label": "Subtitle"
        },
        {
          "type": "text",
          "id": "cta_label",
          "label": "CTA label",
          "placeholder": "Shop now"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Collection feature grid",
      "category": "Collection",
      "blocks": [
        { "type": "card" },
        { "type": "card" },
        { "type": "card" }
      ]
    }
  ]
}
{% endschema %}

How to add the section

  1. 01
    Open your theme code editor.

    In Shopify Admin, go to Online Store → Themes. On your active theme, click the three-dot menu and select Edit code.

  2. 02
    Create a new section file.

    Under the Sections folder, click Add a new section. Name it jlx-collection-grid and click Done.

  3. 03
    Paste the full section code.

    Delete any placeholder content in the new file, paste the entire code block above, and save.

  4. 04
    Add the section to a template.

    Open the theme editor (Customize). Navigate to the page template where you want the grid. Click Add section and select Collection feature grid.

  5. 05
    Add collection cards as blocks.

    Inside the section panel, click Add block for each card (up to 12). Pick the collection from the picker. Optionally override the image, title, subtitle, or CTA label per card. Save.

  6. 06
    Tune layout and behaviour.

    In the section settings, choose columns_desktop (2-5), columns_mobile (1 or 2), the card style, and the image aspect ratio. Toggle product count and subtitle visibility. Adjust gap and padding to match your theme's rhythm.

Schema settings

Section-level settings (apply to the whole grid):

SettingTypeNotes
headingtextOptional heading above the grid.
subheadingtextOptional subheading below the heading.
columns_desktopselectColumns on screens ≥ 990px. Choose from 2, 3, 4, or 5. Default 3.
columns_mobileselectColumns on screens < 750px. 1 or 2. Default 1.
card_styleselectimage_above (default: image on top, text below), image_overlay (text on top of image with gradient), or image_left (image beside text on desktop).
aspect_ratioselectImage aspect ratio: square (default), portrait (3:4), landscape (4:3), or auto (original).
show_countcheckboxRender the collection's product count below the title. Default on.
show_subtitlecheckboxRender the per-card subtitle if provided. Default on.
gaprangeGap between cards. 0-60px in 4px steps. Default 20px.
padding_toprangeTop padding of the section. 0-160px in 4px steps. Default 60px.
padding_bottomrangeBottom padding of the section. 0-160px in 4px steps. Default 60px.

Block settings (per card, up to 12 cards):

SettingTypeNotes
collectioncollectionRequired. The collection this card links to. Cards with no collection are skipped.
image_overrideimage_pickerOptional custom image. Falls back to collection.image, then collection.products.first.featured_image.
title_overridetextOptional custom title. Falls back to the collection title.
subtitletextOptional tagline shown below the title when show_subtitle is on.
cta_labeltextOptional CTA copy (e.g. "Shop now"). Whole card is already clickable.

How it works

The grid is a CSS grid with three breakpoints. On mobile it uses columns_mobile (1 or 2), between 750px and 990px it forces 2 columns for tablets, and at 990px and above it uses columns_desktop (2 to 5). Both column counts are passed as CSS custom properties --jlx-cg-cols-d and --jlx-cg-cols-m set inline from Liquid, so the actual column count always matches the picked value.

Each card resolves its image with a three-step fallback: block.settings.image_override if set, otherwise collection.image, otherwise collection.products.first.featured_image. That means a brand-new collection with no products and no image still renders an empty media slot rather than breaking the layout, and a stylist can hand-pick a hero image per card without touching the underlying collection.

Images use Shopify's image_url filter with a responsive srcset at 400w, 600w, 800w, and 1200w. The sizes attribute matches the column count so the browser fetches the smallest viable file. Native loading="lazy" defers offscreen images, and explicit width / height from the source asset prevents layout shift.

Card style is a CSS variant. The outer <section> gets a class like jlx-cg--image_overlay, jlx-cg--image_above, or jlx-cg--image_left, and CSS swaps the layout. Overlay mode positions the body absolutely over the media with a dark gradient and white text. Left mode switches the card to a 40/60 grid at desktop only. Above is the default vertical stack.

Aspect ratio is set with a CSS aspect-ratio declaration on the media slot, scoped per variant class (jlx-cg--ratio-square, jlx-cg--ratio-portrait, etc.). The image inside fills the slot with object-fit: cover, so any source crops cleanly without distortion.

Compatibility

Tested against Dawn 12+ and Horizon, and works on Sense, Craft, Refresh, and most OS 2.0 themes. The section reads --color-foreground, --color-background, --color-background-2, --color-foreground-secondary, and --color-border from the theme so it picks up your brand colours automatically.

On themes that do not define these CSS custom properties, the section falls back to neutral defaults (near-black foreground, white background, light grey muted tones). To force brand colours, override .jlx-cg__title, .jlx-cg__count, and .jlx-cg__cta in the section CSS.

aspect-ratio CSS is supported in Safari 15+, Chrome 88+, and Firefox 89+, which covers every browser Shopify themes target. Shopify's storefront renderer requires a modern browser, so this is not a concern for OS 2.0 themes.

Limitations

  • Twelve cards maximum: max_blocks: 12 is set in the schema. Most homepages use 3 to 6. To raise the cap, change the value in the schema.
  • Five desktop columns maximum: the columns_desktop select tops out at 5. Beyond five columns each card becomes too narrow for the title and product count to read comfortably. Add more options to the select if you need a denser layout.
  • One image per card: the section renders a single image per card. Hover-swap or carousel-in-card behaviour is not built in. Wire up a second image_picker setting and a CSS hover rule to add it.
  • Product count is server-rendered: collection.products_count reflects the count at render time and does not update if products are added later without a cache bust. Shopify auto-invalidates on collection changes, so this is rarely visible in practice.
  • Headless storefronts: this is a Liquid section and does not apply to Hydrogen or other headless setups.
Shopify Section Build

Need this built for your store?

If you want the collection grid styled to match your brand, extended with hover swaps, animation, or wired up to merchandised metafields, we can scope and deliver that.