# Image banner grid: free Shopify section

> Two to four linked promo tiles in a mosaic or row, each with desktop and mobile images, heading and button. Whole tile clickable, stacks or swipes on mobile.

Image banner grid is a free Shopify section that puts two to four linked promo tiles side by side, in a mosaic with one tall tile or in an equal row, for campaigns and category entry points. Each tile has a desktop and a mobile image with focal points, a heading, a short line and a button-style label, placed per device over a soft shade that keeps the text readable on any photo. A linked tile is a single link, so the whole tile is the click target; on mobile the tiles stack or become a swipeable row. Pure Liquid and CSS for Online Store 2.0 themes, without an app.

- **Category:** Hero and banners
- **Use on:** Home, Landing pages, Collection
- **Reads:** Content only
- **Size:** 23 KB in 2 files, no JavaScript
- **License:** MIT, © 2026 Lintel
- **Page:** https://lintelagency.com/resources/shopify-sections/image-banner-grid
- **Live demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-image-banner-grid.html
- **Added:** 2026-11-02

## What it’s for

- Home page entry points to three or four main categories, under the hero
- A seasonal campaign block: the sale, new arrivals and gifts in one mosaic
- A collection page header that links to sub-collections

## Editor settings

In the theme editor the section is called “Lib · Image banner grid”. IDs, types, labels and defaults come from the section’s schema; the labels are what the theme editor shows.

### Content

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `heading` | `inline_richtext` | Heading | – |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large` |
| `text` | `richtext` | Intro text | – |  |
| `new_tab_label` | `text` | New tab notice (screen readers) | opens in a new tab |  |

### Tiles

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `layout` | `select` | Layout on desktop | mosaic | `mosaic`, `row` |
| `layout_mobile` | `select` | Layout on mobile | stack | `stack`, `row` |
| `tile_height` | `range` | Tile height on desktop | `300` | 200–640 px, step 10 |
| `tile_height_mobile` | `range` | Tile height on mobile | `280` | 160–520 px, step 10 |
| `gap` | `range` | Space between tiles | `12` | 0–32 px, step 2 |
| `radius` | `range` | Corner radius | `14` | 0–32 px, step 2 |
| `tile_heading_size` | `select` | Tile heading size | medium | `small`, `medium`, `large` |
| `overlay_opacity` | `range` | Shade behind tile text | `55` | 0–90 %, step 5 |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `select` | Heading alignment on desktop | left | `left`, `center` |
| `text_alignment_mobile` | `select` | Heading alignment on mobile | left | `left`, `center` |

### Colors

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #FFFFFF |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #1F1B17 |  |
| `color_tile_text` | `color` | Tile text | #FFFFFF |  |
| `color_overlay` | `color` | Tile shade | #1F1B17 |  |
| `color_accent` | `color` | Solid button | #FFFFFF |  |
| `color_accent_text` | `color` | Solid button text | #1F1B17 |  |

### Spacing and visibility

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `padding_top` | `range` | Top padding on desktop | `48` | 0–200 px, step 4 |
| `padding_bottom` | `range` | Bottom padding on desktop | `48` | 0–200 px, step 4 |
| `padding_top_mobile` | `range` | Top padding on mobile | `32` | 0–160 px, step 4 |
| `padding_bottom_mobile` | `range` | Bottom padding on mobile | `32` | 0–160 px, step 4 |
| `show_on` | `select` | Show on | all | `all`, `desktop`, `mobile` |
| `anchor_id` | `text` | Anchor ID | – |  |

### Motion

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `animate` | `checkbox` | Zoom images slightly on hover | `true` |  |

## Blocks

Up to 4 blocks in total.

### Tile (`tile`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `image` | `image_picker` | Image | – |  |
| `image_mobile` | `image_picker` | Mobile image | – |  |
| `heading` | `text` | Heading | Tile heading |  |
| `text` | `text` | Text | – |  |
| `cta_label` | `text` | Button text | Shop now |  |
| `link` | `url` | Link | – |  |
| `new_tab` | `checkbox` | Open link in a new tab | `false` |  |
| `cta_style` | `select` | Button style | primary | `primary`, `outline`, `text` |
| `position` | `select` | Text position on desktop | bottom-left | `bottom-left`, `bottom-center`, `top-left`, `center` |
| `position_mobile` | `select` | Text position on mobile | bottom-left | `bottom-left`, `bottom-center`, `top-left`, `center` |

## Install

### In Shopify admin

1. Work on a copy, not your live theme: in Shopify admin go to **Online Store > Themes**, open the **…** menu on your current theme and choose **Duplicate**.
2. On the copy, open the **…** menu and choose **Edit code**.
3. In the `sections` folder, add a new file named `lib-image-banner-grid.liquid` and paste the contents of `sections/lib-image-banner-grid.liquid` from the source below. Save.
4. In the `snippets` folder, add `lib-image.liquid` the same way. If a snippet is already there from another Lib section, it is the same file; keep one copy.
5. Open **Customize** on the copy, choose **Add section** and pick **Lib · Image banner grid**. Replace the sample content with your own and set the colors.
6. Preview the copy, then publish it when it looks right.

### With Shopify CLI

From your local theme folder, download the files and push only them to an unpublished theme (find its ID with `shopify theme list`):

```sh
curl -fsSL -o sections/lib-image-banner-grid.liquid https://lintelagency.com/resources/shopify-sections/files/lib-image-banner-grid.liquid
curl -fsSL -o snippets/lib-image.liquid https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-image-banner-grid.liquid --only snippets/lib-image.liquid
```

Never push to the live theme: always pass the ID of an unpublished theme and never use `--live` or `--allow-live`. Then add the section in the theme editor as above.

## Files

- [sections/lib-image-banner-grid.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-image-banner-grid.liquid): goes in `sections/`
- [snippets/lib-image.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid): goes in `snippets/`

## Questions

### How many tiles can I add, and how do they arrange?

Two to four. In the mosaic layout three or four tiles put the first one tall on the left; with four, the last one runs across the bottom right. The equal row puts them side by side (two per row on tablets when there are four). On mobile they stack or swipe.

### Can I use different images on mobile?

Yes. Each tile has a desktop image and an optional mobile image, and both honour the focal point you set in Shopify's image editor, so the subject stays in frame at every size.

### Is the whole tile clickable?

Yes, when the tile has a link: the tile is one link containing the heading, line and button label, so shoppers can tap anywhere and screen readers hear one clear link. Tiles without a link are plain images with text.

### Will white text stay readable on light photos?

A shade fades in from the text's side; you set its color and strength (0–90%) once for the section. Text position is set per tile and per device.

## Source

### sections/lib-image-banner-grid.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Image banner grid: two to four linked promo tiles in a mosaic or a row, for campaigns and
  category entry points. Each tile has a desktop and a mobile image (focal points honoured), a
  heading, a short line and a call to action, with the text placed per device over a soft
  gradient. A linked tile is one link, so the whole tile is the target. On mobile the tiles stack
  or become a swipeable row. Pure Liquid and CSS; no JavaScript.
  Depends on: snippets/lib-image.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign tile_tag = 'h3'
  if heading_tag == 'h3' or heading_tag == 'p'
    assign tile_tag = 'h4'
  endif
  assign count = section.blocks.size
-%}

<div
  class="lib-bgrid lib-bgrid--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-layout="{{ s.layout }}"
  data-layout-m="{{ s.layout_mobile }}"
  data-count="{{ count }}"
  {% if s.animate %}
    data-animate
  {% endif %}
  style="
    --lib-bg: {{ s.color_bg }};
    {% if s.color_bg_gradient != blank %}--lib-bg-image: {{ s.color_bg_gradient }};{% endif %}
    --lib-text: {{ s.color_text }};
    --lib-tile-text: {{ s.color_tile_text }};
    --lib-overlay: {{ s.color_overlay }};
    --lib-overlay-strength: {{ s.overlay_opacity }}%;
    --lib-accent: {{ s.color_accent }};
    --lib-accent-text: {{ s.color_accent_text }};
    --lib-h: {{ s.tile_height }}px;
    --lib-h-m: {{ s.tile_height_mobile }}px;
    --lib-gap: {{ s.gap }}px;
    --lib-radius: {{ s.radius }}px;
    --lib-pt: {{ s.padding_top }}px;
    --lib-pb: {{ s.padding_bottom }}px;
    --lib-pt-m: {{ s.padding_top_mobile }}px;
    --lib-pb-m: {{ s.padding_bottom_mobile }}px;
  "
>
  <div class="lib-bgrid__inner">
    {%- if s.heading != blank or s.text != blank -%}
      <header class="lib-bgrid__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-bgrid__heading lib-bgrid__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-bgrid__text">{{ s.text }}</div>
        {%- endif -%}
      </header>
    {%- endif -%}

    {%- if count > 0 -%}
      <ul class="lib-bgrid__grid" role="list">
        {%- for block in section.blocks -%}
          {%- liquid
            assign b = block.settings
            assign wrapper = 'div'
            if b.link != blank
              assign wrapper = 'a'
            endif
            assign sizes = '(min-width: 990px) 50vw, 100vw'
            if s.layout == 'row' and count > 2
              assign sizes = '(min-width: 990px) 33vw, 100vw'
            endif
            assign eager = false
            if section.index == 1 and forloop.index <= 2
              assign eager = true
            endif
          -%}
          <li class="lib-bgrid__item" {{ block.shopify_attributes }}>
            <{{ wrapper }}
              class="lib-bgrid__tile"
              data-pos="{{ b.position }}"
              data-pos-m="{{ b.position_mobile }}"
              {% if b.link != blank %}
                href="{{ b.link }}"
                {% if b.new_tab %}
                  target="_blank" rel="noopener"
                {% endif %}
              {% endif %}
            >
              <div class="lib-bgrid__media">
                {%- render 'lib-image', image: b.image, image_mobile: b.image_mobile, class: 'lib-bgrid__img', sizes: sizes, eager: eager -%}
              </div>
              {%- if b.heading != blank or b.text != blank or b.cta_label != blank -%}
                <div class="lib-bgrid__content">
                  {%- if b.heading != blank -%}
                    <{{ tile_tag }} class="lib-bgrid__title lib-bgrid__title--{{ s.tile_heading_size }}">{{ b.heading | escape }}</{{ tile_tag }}>
                  {%- endif -%}
                  {%- if b.text != blank -%}
                    <p class="lib-bgrid__line">{{ b.text | escape }}</p>
                  {%- endif -%}
                  {%- if b.cta_label != blank -%}
                    <span class="lib-bgrid__cta lib-bgrid__cta--{{ b.cta_style }}">
                      {{- b.cta_label | escape -}}
                      <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3 8h9M8.5 4.5 12 8l-3.5 3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    </span>
                  {%- endif -%}
                  {%- if b.link != blank and b.new_tab and s.new_tab_label != blank -%}
                    <span class="lib-bgrid__sr">({{ s.new_tab_label | escape }})</span>
                  {%- endif -%}
                </div>
              {%- endif -%}
            </{{ wrapper }}>
          </li>
        {%- endfor -%}
      </ul>
    {%- endif -%}
  </div>
</div>

{% stylesheet %}
  .lib-bgrid {
    background-color: var(--lib-bg);
    background-image: var(--lib-bg-image, none);
    color: var(--lib-text);
    padding: var(--lib-pt) 1rem var(--lib-pb);
  }
  .lib-bgrid__inner { max-width: 1200px; margin: 0 auto; }
  .lib-bgrid--full { padding-inline: var(--lib-gap); }
  .lib-bgrid--full .lib-bgrid__inner { max-width: none; }
  @media (max-width: 749px) {
    .lib-bgrid { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-bgrid[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-bgrid[data-show='mobile'] { display: none; }
  }

  .lib-bgrid__header { max-width: 42rem; margin-bottom: clamp(1.25rem, 3vw, 2rem); }
  .lib-bgrid__heading {
    margin: 0;
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .lib-bgrid__heading--small { font-size: clamp(1.25rem, 1.8vw, 1.5rem); }
  .lib-bgrid__heading--large { font-size: clamp(2rem, 4vw, 3.25rem); }
  .lib-bgrid__text { margin-top: 0.6rem; font-size: 1rem; line-height: 1.6; opacity: 0.85; }
  .lib-bgrid__text > :first-child { margin-top: 0; }
  .lib-bgrid__text > :last-child { margin-bottom: 0; }
  .lib-bgrid__header[data-align='center'] { margin-inline: auto; text-align: center; }
  @media (max-width: 749px) {
    .lib-bgrid__header[data-align-m='left'] { margin-inline: 0; text-align: left; }
    .lib-bgrid__header[data-align-m='center'] { margin-inline: auto; text-align: center; }
  }

  /* Row: equal tiles side by side. Mosaic: the first tile spans two rows on the left. */
  .lib-bgrid__grid {
    display: grid;
    grid-template-columns: repeat(var(--lib-cols, 2), minmax(0, 1fr));
    grid-auto-rows: var(--lib-h);
    gap: var(--lib-gap);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .lib-bgrid[data-count='3'] .lib-bgrid__grid { --lib-cols: 3; }
  .lib-bgrid[data-count='4'] .lib-bgrid__grid { --lib-cols: 4; }
  .lib-bgrid[data-count='1'] .lib-bgrid__grid { --lib-cols: 1; }
  @media (min-width: 750px) {
    .lib-bgrid[data-layout='mosaic'][data-count='3'] .lib-bgrid__grid {
      grid-template-columns: 3fr 2fr;
      grid-auto-rows: calc((var(--lib-h) * 2 - var(--lib-gap)) / 2);
    }
    .lib-bgrid[data-layout='mosaic'][data-count='3'] .lib-bgrid__item:first-child { grid-row: span 2; }
    .lib-bgrid[data-layout='mosaic'][data-count='4'] .lib-bgrid__grid {
      grid-template-columns: 3fr 2fr 2fr;
      grid-auto-rows: calc((var(--lib-h) * 2 - var(--lib-gap)) / 2);
    }
    .lib-bgrid[data-layout='mosaic'][data-count='4'] .lib-bgrid__item:first-child { grid-row: span 2; }
    .lib-bgrid[data-layout='mosaic'][data-count='4'] .lib-bgrid__item:last-child { grid-column: span 2; }
  }
  @media (min-width: 750px) and (max-width: 989px) {
    .lib-bgrid[data-layout='row'][data-count='4'] .lib-bgrid__grid { --lib-cols: 2; }
  }
  @media (max-width: 749px) {
    .lib-bgrid__grid { grid-template-columns: 1fr; grid-auto-rows: var(--lib-h-m); }
    .lib-bgrid[data-layout-m='row'] .lib-bgrid__grid {
      grid-auto-flow: column;
      grid-template-columns: none;
      grid-auto-columns: 82%;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scrollbar-width: none;
    }
    .lib-bgrid[data-layout-m='row'] .lib-bgrid__item { scroll-snap-align: start; }
  }

  .lib-bgrid__item { display: flex; min-width: 0; min-height: 0; }
  .lib-bgrid__tile {
    position: relative;
    display: flex;
    flex: 1;
    overflow: hidden;
    border-radius: var(--lib-radius);
    color: var(--lib-tile-text);
    text-decoration: none;
    isolation: isolate;
  }
  .lib-bgrid__media { position: absolute; inset: 0; z-index: -2; background: color-mix(in srgb, var(--lib-text) 10%, transparent); }
  .lib-bgrid__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.8s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  svg.lib-bgrid__img { fill: color-mix(in srgb, var(--lib-text) 25%, transparent); }
  /* A gradient towards the text keeps it readable on any photo. */
  .lib-bgrid__tile::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(to top, color-mix(in srgb, var(--lib-overlay) var(--lib-overlay-strength), transparent), transparent 70%);
  }
  .lib-bgrid__tile[data-pos^='top']::before { background: linear-gradient(to bottom, color-mix(in srgb, var(--lib-overlay) var(--lib-overlay-strength), transparent), transparent 70%); }
  .lib-bgrid__tile[data-pos='center']::before { background: color-mix(in srgb, var(--lib-overlay) calc(var(--lib-overlay-strength) * 0.7), transparent); }

  .lib-bgrid__content {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    width: 100%;
    padding: clamp(1.125rem, 2.5vw, 2rem);
  }
  .lib-bgrid__tile[data-pos='bottom-left'] .lib-bgrid__content { margin-top: auto; align-items: flex-start; text-align: left; }
  .lib-bgrid__tile[data-pos='bottom-center'] .lib-bgrid__content { margin-top: auto; align-items: center; text-align: center; }
  .lib-bgrid__tile[data-pos='top-left'] .lib-bgrid__content { align-items: flex-start; text-align: left; }
  .lib-bgrid__tile[data-pos='center'] .lib-bgrid__content { margin-block: auto; align-items: center; text-align: center; }
  @media (max-width: 749px) {
    .lib-bgrid__tile[data-pos-m='bottom-left'] .lib-bgrid__content { margin-block: auto 0; align-items: flex-start; text-align: left; }
    .lib-bgrid__tile[data-pos-m='bottom-center'] .lib-bgrid__content { margin-block: auto 0; align-items: center; text-align: center; }
    .lib-bgrid__tile[data-pos-m='top-left'] .lib-bgrid__content { margin-block: 0 auto; align-items: flex-start; text-align: left; }
    .lib-bgrid__tile[data-pos-m='center'] .lib-bgrid__content { margin-block: auto; align-items: center; text-align: center; }
  }

  .lib-bgrid__title { margin: 0; font-size: clamp(1.25rem, 2vw, 1.625rem); line-height: 1.2; letter-spacing: -0.01em; text-wrap: balance; }
  .lib-bgrid__title--small { font-size: clamp(1.0625rem, 1.5vw, 1.25rem); }
  .lib-bgrid__title--large { font-size: clamp(1.5rem, 2.8vw, 2.25rem); }
  .lib-bgrid__line { margin: 0; max-width: 32rem; font-size: 0.9375rem; line-height: 1.5; }
  .lib-bgrid__cta {
    display: inline-flex;
    align-items: center;
    gap: 0.45em;
    margin-top: 0.5rem;
    padding: 0.7em 1.25em;
    border-radius: 999px;
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1;
  }
  .lib-bgrid__cta--primary { background: var(--lib-accent); color: var(--lib-accent-text); }
  .lib-bgrid__cta--outline { box-shadow: inset 0 0 0 1.5px currentColor; }
  .lib-bgrid__cta--text { padding-inline: 0; text-decoration: underline; text-underline-offset: 0.25em; }
  .lib-bgrid__cta svg { transition: transform 0.25s ease; }
  .lib-bgrid__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  a.lib-bgrid__tile:focus-visible { outline: 3px solid var(--lib-accent); outline-offset: 3px; }
  @media (hover: hover) {
    a.lib-bgrid__tile:hover .lib-bgrid__cta svg { transform: translateX(3px); }
  }
  @media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .lib-bgrid[data-animate] a.lib-bgrid__tile:hover .lib-bgrid__img { transform: scale(1.04); }
  }
  @media (prefers-reduced-motion: reduce) {
    .lib-bgrid__img,
    .lib-bgrid__cta svg { transition: none; }
  }
{% endstylesheet %}

{% schema %}
{
  "name": "Lib · Image banner grid",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 4,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading" },
    {
      "type": "select",
      "id": "heading_tag",
      "label": "Heading tag",
      "info": "Use H1 only when this is the page's main heading.",
      "options": [
        { "value": "h1", "label": "H1" },
        { "value": "h2", "label": "H2" },
        { "value": "h3", "label": "H3" },
        { "value": "p", "label": "Paragraph" }
      ],
      "default": "h2"
    },
    {
      "type": "select",
      "id": "heading_size",
      "label": "Heading size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "medium", "label": "Medium" },
        { "value": "large", "label": "Large" }
      ],
      "default": "medium"
    },
    { "type": "richtext", "id": "text", "label": "Intro text" },
    { "type": "text", "id": "new_tab_label", "label": "New tab notice (screen readers)", "default": "opens in a new tab" },

    { "type": "header", "content": "Tiles" },
    {
      "type": "select",
      "id": "layout",
      "label": "Layout on desktop",
      "info": "Mosaic makes the first tile tall when there are three or four tiles.",
      "options": [
        { "value": "mosaic", "label": "Mosaic" },
        { "value": "row", "label": "Equal row" }
      ],
      "default": "mosaic"
    },
    {
      "type": "select",
      "id": "layout_mobile",
      "label": "Layout on mobile",
      "options": [
        { "value": "stack", "label": "Stacked" },
        { "value": "row", "label": "Swipeable row" }
      ],
      "default": "stack"
    },
    { "type": "range", "id": "tile_height", "label": "Tile height on desktop", "min": 200, "max": 640, "step": 10, "unit": "px", "default": 300 },
    { "type": "range", "id": "tile_height_mobile", "label": "Tile height on mobile", "min": 160, "max": 520, "step": 10, "unit": "px", "default": 280 },
    { "type": "range", "id": "gap", "label": "Space between tiles", "min": 0, "max": 32, "step": 2, "unit": "px", "default": 12 },
    { "type": "range", "id": "radius", "label": "Corner radius", "min": 0, "max": 32, "step": 2, "unit": "px", "default": 14 },
    {
      "type": "select",
      "id": "tile_heading_size",
      "label": "Tile heading size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "medium", "label": "Medium" },
        { "value": "large", "label": "Large" }
      ],
      "default": "medium"
    },
    { "type": "range", "id": "overlay_opacity", "label": "Shade behind tile text", "min": 0, "max": 90, "step": 5, "unit": "%", "default": 55 },
    {
      "type": "select",
      "id": "section_width",
      "label": "Width",
      "options": [
        { "value": "page", "label": "Page width" },
        { "value": "full", "label": "Full width" }
      ],
      "default": "page"
    },
    {
      "type": "select",
      "id": "text_alignment",
      "label": "Heading alignment on desktop",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" }
      ],
      "default": "left"
    },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Heading alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" }
      ],
      "default": "left"
    },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#FFFFFF" },
    { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." },
    { "type": "color", "id": "color_text", "label": "Text", "default": "#1F1B17" },
    { "type": "color", "id": "color_tile_text", "label": "Tile text", "default": "#FFFFFF" },
    { "type": "color", "id": "color_overlay", "label": "Tile shade", "default": "#1F1B17" },
    { "type": "color", "id": "color_accent", "label": "Solid button", "default": "#FFFFFF" },
    { "type": "color", "id": "color_accent_text", "label": "Solid button text", "default": "#1F1B17" },

    { "type": "header", "content": "Spacing and visibility" },
    { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 48 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 48 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 32 },
    { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 32 },
    {
      "type": "select",
      "id": "show_on",
      "label": "Show on",
      "options": [
        { "value": "all", "label": "All devices" },
        { "value": "desktop", "label": "Desktop only" },
        { "value": "mobile", "label": "Mobile only" }
      ],
      "default": "all"
    },
    { "type": "text", "id": "anchor_id", "label": "Anchor ID", "info": "Optional. Link to this section with #your-id." },

    { "type": "header", "content": "Motion" },
    { "type": "checkbox", "id": "animate", "label": "Zoom images slightly on hover", "info": "Never when reduced motion is on.", "default": true }
  ],
  "blocks": [
    {
      "type": "tile",
      "name": "Tile",
      "settings": [
        { "type": "image_picker", "id": "image", "label": "Image" },
        { "type": "image_picker", "id": "image_mobile", "label": "Mobile image", "info": "Optional. Below 750px." },
        { "type": "text", "id": "heading", "label": "Heading", "default": "Tile heading" },
        { "type": "text", "id": "text", "label": "Text" },
        { "type": "text", "id": "cta_label", "label": "Button text", "default": "Shop now" },
        { "type": "url", "id": "link", "label": "Link", "info": "The whole tile becomes the link." },
        { "type": "checkbox", "id": "new_tab", "label": "Open link in a new tab", "default": false },
        {
          "type": "select",
          "id": "cta_style",
          "label": "Button style",
          "options": [
            { "value": "primary", "label": "Solid" },
            { "value": "outline", "label": "Outline" },
            { "value": "text", "label": "Text link" }
          ],
          "default": "primary"
        },
        {
          "type": "select",
          "id": "position",
          "label": "Text position on desktop",
          "options": [
            { "value": "bottom-left", "label": "Bottom left" },
            { "value": "bottom-center", "label": "Bottom center" },
            { "value": "top-left", "label": "Top left" },
            { "value": "center", "label": "Center" }
          ],
          "default": "bottom-left"
        },
        {
          "type": "select",
          "id": "position_mobile",
          "label": "Text position on mobile",
          "options": [
            { "value": "bottom-left", "label": "Bottom left" },
            { "value": "bottom-center", "label": "Bottom center" },
            { "value": "top-left", "label": "Top left" },
            { "value": "center", "label": "Center" }
          ],
          "default": "bottom-left"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Image banner grid",
      "blocks": [
        { "type": "tile", "settings": { "heading": "New season" } },
        { "type": "tile", "settings": { "heading": "Best sellers" } },
        { "type": "tile", "settings": { "heading": "Gifts" } }
      ]
    }
  ]
}
{% endschema %}
```

### snippets/lib-image.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{% doc %}
  Responsive image with optional art direction: a separate mobile image (below 750px),
  each honouring its own focal point from the theme editor. Falls back to a placeholder.

  @param {image} [image] - Desktop image (also used on mobile when no mobile image is set)
  @param {image} [image_mobile] - Mobile image, below 750px
  @param {string} [class] - Class for the img element
  @param {string} [sizes] - sizes attribute for the desktop image (default 100vw)
  @param {boolean} [eager] - Load immediately with high fetch priority (use for the first sections on a page)
  @param {string} [placeholder] - placeholder_svg_tag name when no image is set (default 'lifestyle-1')

  @example
  {% render 'lib-image', image: section.settings.image, image_mobile: section.settings.image_mobile, class: 'lib-hero__img', sizes: '(min-width: 990px) 50vw, 100vw', eager: true %}
{% enddoc %}

{%- liquid
  assign desktop = image
  if desktop == blank
    assign desktop = image_mobile
  endif
  assign img_sizes = sizes | default: '100vw'
  assign img_loading = 'lazy'
  assign img_priority = 'auto'
  if eager
    assign img_loading = 'eager'
    assign img_priority = 'high'
  endif
  assign art = false
  if image != blank and image_mobile != blank
    assign art = true
  endif
-%}

{%- if desktop != blank -%}
  <picture
    class="lib-picture{% if art %} lib-picture--art{% endif %}"
    {% if art %}
      style="--lib-pos-m: {{ image_mobile.presentation.focal_point | default: '50% 50%' }};"
    {% endif %}
  >
    {%- if art -%}
      <source
        media="(max-width: 749px)"
        srcset="
          {{ image_mobile | image_url: width: 375 }} 375w,
          {{ image_mobile | image_url: width: 750 }} 750w,
          {{ image_mobile | image_url: width: 1100 }} 1100w,
          {{ image_mobile | image_url: width: 1500 }} 1500w
        "
        sizes="100vw"
        width="{{ image_mobile.width }}"
        height="{{ image_mobile.height }}"
      >
    {%- endif -%}
    {{
      desktop
      | image_url: width: 2400
      | image_tag:
        class: class,
        loading: img_loading,
        fetchpriority: img_priority,
        sizes: img_sizes,
        widths: '600, 900, 1200, 1600, 2000, 2400'
    }}
  </picture>
{%- else -%}
  {{ placeholder | default: 'lifestyle-1' | placeholder_svg_tag: class }}
{%- endif -%}

{% stylesheet %}
  .lib-picture { display: contents; }
  @media (max-width: 749px) {
    /* image_tag sets the desktop focal point inline; the mobile image needs its own. */
    .lib-picture--art img { object-position: var(--lib-pos-m) !important; }
  }
{% endstylesheet %}
```

## License

MIT License, © 2026 Lintel. Free to use, change and ship in your own store or in client work; keep the license line at the top of each file.
