# Shipping and returns: free Shopify section

> Delivery times, thresholds and the returns promise as icon points, a delivery date estimate from your own day counts, and links to the store's policies.

Shipping and returns is a free Shopify section that puts delivery and returns information where shoppers decide: icon points for delivery times, free-shipping thresholds and the returns window, an optional delivery estimate that turns your own minimum and maximum days into dates such as "Thu, Nov 5 – Mon, Nov 9", and links to the shipping and refund policies set in the Shopify admin. It works as a row on a home page or a compact list under a product's buy box in Online Store 2.0 themes, without an app.

- **Category:** Product page
- **Use on:** Product, Home, Cart, Landing pages
- **Reads:** Content only, plus the store's shipping and refund policies
- **Size:** 27 KB in 2 files, with a small script
- **License:** MIT, © 2026 Lintel
- **Page:** https://lintelagency.com/resources/shopify-sections/shipping-returns
- **Live demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-shipping-returns.html
- **Added:** 2026-11-02

## What it’s for

- A compact list under the buy box on product templates: delivery time, returns window, international shipping
- A row of delivery and returns promises on the home page or above the footer
- A delivery date estimate on product or cart pages before a holiday cut-off

## Editor settings

In the theme editor the section is called “Lib · Shipping & returns”. 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 | Delivery and returns |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large` |
| `text` | `richtext` | Intro text | – |  |

### Store policies

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `show_policy_links` | `checkbox` | Link to the store's shipping and refund policies | `true` |  |
| `shipping_policy_label` | `text` | Shipping policy link text | Shipping policy |  |
| `refund_policy_label` | `text` | Refund policy link text | Returns and refunds |  |

### Layout

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `layout` | `select` | Layout | row | `row`, `list` |
| `item_style` | `select` | Item style | plain | `plain`, `card`, `divided` |
| `columns` | `range` | Columns on desktop | `4` | 1–4, step 1 |
| `columns_mobile` | `range` | Columns on mobile | `1` | 1–2, step 1 |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `select` | Alignment on desktop | left | `left`, `center` |
| `text_alignment_mobile` | `select` | 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_accent` | `color` | Accent (icons) | #7A5A3A |  |
| `color_accent_text` | `color` | Text on accent | #FFFFFF |  |
| `color_card` | `color` | Card background | #FAF6F1 |  |
| `color_border` | `color` | Borders | #E7E0D8 |  |

### Spacing and visibility

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `padding_top` | `range` | Top padding on desktop | `64` | 0–200 px, step 4 |
| `padding_bottom` | `range` | Bottom padding on desktop | `64` | 0–200 px, step 4 |
| `padding_top_mobile` | `range` | Top padding on mobile | `40` | 0–160 px, step 4 |
| `padding_bottom_mobile` | `range` | Bottom padding on mobile | `40` | 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` | Fade in on scroll | `false` |  |

## Blocks

Up to 8 blocks in total.

### Point (`item`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `icon` | `select` | Icon | truck | `truck`, `return`, `clock`, `calendar`, `globe`, `box`, `store`, `shield`, `card`, `gift`, `leaf` |
| `icon_image` | `image_picker` | Custom icon | – |  |
| `title` | `text` | Title | Free delivery over $75 |  |
| `text` | `inline_richtext` | Text | Tracked delivery in 2 to 4 business days. |  |
| `link_label` | `text` | Link text | – |  |
| `link_url` | `url` | Link | – |  |
| `link_new_tab` | `checkbox` | Open link in a new tab | `false` |  |

### Delivery estimate (`estimate`, up to 1)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `icon` | `select` | Icon | calendar | `calendar`, `truck`, `clock`, `box` |
| `icon_image` | `image_picker` | Custom icon | – |  |
| `title` | `text` | Title | Estimated delivery |  |
| `min_days` | `range` | Earliest delivery, days from today | `3` | 0–30, step 1 |
| `max_days` | `range` | Latest delivery, days from today | `5` | 0–45, step 1 |
| `business_days` | `checkbox` | Count business days only (skip weekends) | `true` |  |
| `estimate_text` | `text` | Estimate text | Order today, arrives {dates} |  |
| `fallback_text` | `text` | Text without dates | Arrives in 3 to 5 business days |  |
| `link_label` | `text` | Link text | – |  |
| `link_url` | `url` | Link | – |  |
| `link_new_tab` | `checkbox` | Open link in a new tab | `false` |  |

## 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-shipping-returns.liquid` and paste the contents of `sections/lib-shipping-returns.liquid` from the source below. Save.
4. In the `snippets` folder, add `lib-button.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 · Shipping & returns**. 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-shipping-returns.liquid https://lintelagency.com/resources/shopify-sections/files/lib-shipping-returns.liquid
curl -fsSL -o snippets/lib-button.liquid https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-shipping-returns.liquid --only snippets/lib-button.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-shipping-returns.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-shipping-returns.liquid): goes in `sections/`
- [snippets/lib-button.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid): goes in `snippets/`

## Questions

### Where do the delivery dates come from?

From the earliest and latest number of days you set on the Delivery estimate block. The section counts them from the shopper's today, skipping weekends if you choose business days, and writes the dates in the page's language. Use your real handling plus transit times: the section doesn't read carrier data.

### What shows if the script doesn't run?

The block's "text without dates" line, for example "Arrives in 3 to 5 business days". The dates replace it only once the small script has worked them out.

### Does it link to my shipping and refund policies?

Yes, when the option is on and the policy exists under Settings > Policies in the Shopify admin. A policy you haven't written gets no link, and the link texts are settings you can change or translate.

### Does it calculate free-shipping thresholds?

No. Thresholds such as "Free delivery over $75" are text you type, so they say exactly what your shipping rates do. The section does not read the cart.

### Can I use my own icons?

Yes. Each point has eleven built-in line icons (delivery, returns, clock, calendar, international, package, store pickup, protection, payment, gift, leaf) or a small image you upload.

## Source

### sections/lib-shipping-returns.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Shipping and returns: delivery times, thresholds and the returns promise as icon points, in a
  row for a home or landing page or a compact list under a product's buy box. An optional delivery
  estimate turns the merchant's own minimum and maximum days into dates in the shopper's language
  (small script; without it the typed fallback line shows). Optional links to the store's own
  shipping and refund policies. Nothing is invented: every number is typed by the merchant.
  Depends on: snippets/lib-button.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign policy_links = false
  if s.show_policy_links
    if shop.shipping_policy != blank or shop.refund_policy != blank
      assign policy_links = true
    endif
  endif
-%}

<div
  class="lib-shipping lib-shipping--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-layout="{{ s.layout }}"
  data-style="{{ s.item_style }}"
  {% 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-accent: {{ s.color_accent }};
    --lib-accent-text: {{ s.color_accent_text }};
    --lib-card: {{ s.color_card }};
    --lib-border: {{ s.color_border }};
    --lib-cols: {{ s.columns }};
    --lib-cols-m: {{ s.columns_mobile }};
    --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-shipping__inner" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
    {%- if s.heading != blank or s.text != blank -%}
      <div class="lib-shipping__header">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-shipping__heading lib-shipping__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-shipping__text">{{ s.text }}</div>
        {%- endif -%}
      </div>
    {%- endif -%}

    {%- if section.blocks.size > 0 -%}
      <ul class="lib-shipping__list" role="list">
        {%- for block in section.blocks -%}
          {%- assign b = block.settings -%}
          <li class="lib-shipping__item" {{ block.shopify_attributes }}>
            <span class="lib-shipping__icon" aria-hidden="true">
              {%- if b.icon_image != blank -%}
                {{ b.icon_image | image_url: width: 96 | image_tag: class: 'lib-shipping__icon-img', loading: 'lazy', alt: '' }}
              {%- else -%}
                <svg viewBox="0 0 24 24" width="24" height="24" focusable="false" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
                  {%- case b.icon -%}
                    {%- when 'return' -%}
                      <path d="M9 14 5 10l4-4"/><path d="M5 10h9a5 5 0 0 1 0 10h-3"/>
                    {%- when 'clock' -%}
                      <circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/>
                    {%- when 'calendar' -%}
                      <rect x="4" y="5.5" width="16" height="14" rx="2"/><path d="M4 10h16M8.5 3.5v4M15.5 3.5v4"/>
                    {%- when 'globe' -%}
                      <circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.5 2.5 3.5 5.5 3.5 8.5s-1 6-3.5 8.5c-2.5-2.5-3.5-5.5-3.5-8.5s1-6 3.5-8.5"/>
                    {%- when 'box' -%}
                      <path d="m12 3 8 4v10l-8 4-8-4V7z"/><path d="m4 7 8 4 8-4M12 11v10"/>
                    {%- when 'store' -%}
                      <path d="M4 9.5 5.5 4h13L20 9.5M4 9.5h16M4 9.5a2.7 2.7 0 0 0 5.3 0 2.7 2.7 0 0 0 5.4 0 2.7 2.7 0 0 0 5.3 0M5.5 12v8h13v-8M10 20v-4.5h4V20"/>
                    {%- when 'shield' -%}
                      <path d="M12 3l7 3v5c0 5-3 8.5-7 10-4-1.5-7-5-7-10V6z"/><path d="m9 12 2 2 4-4"/>
                    {%- when 'card' -%}
                      <rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="M3 10h18M7 15h3"/>
                    {%- when 'gift' -%}
                      <path d="M4 10h16v10H4zM3 7h18v3H3zM12 7v13M12 7c-1.5-3.5-5-3.5-5-1.25S9 7 12 7zM12 7c1.5-3.5 5-3.5 5-1.25S15 7 12 7z"/>
                    {%- when 'leaf' -%}
                      <path d="M5 19c0-8 5-13 14-14 0 9-5 14-13 14M5 19l7-7"/>
                    {%- else -%}
                      <path d="M3 6h11v10H3zM14 9h4l3 3v4h-7"/><circle cx="7" cy="17.5" r="1.8"/><circle cx="17" cy="17.5" r="1.8"/>
                  {%- endcase -%}
                </svg>
              {%- endif -%}
            </span>
            <div class="lib-shipping__body">
              {%- if b.title != blank -%}
                <p class="lib-shipping__title">{{ b.title | escape }}</p>
              {%- endif -%}

              {%- if block.type == 'estimate' -%}
                {%- comment -%} The script swaps the fallback for dates; without it the fallback stays. {%- endcomment -%}
                <p
                  class="lib-shipping__detail lib-shipping__estimate"
                  data-min="{{ b.min_days }}"
                  data-max="{{ b.max_days }}"
                  data-business="{{ b.business_days }}"
                  data-template="{{ b.estimate_text | escape }}"
                >
                  {{- b.fallback_text | escape -}}
                </p>
              {%- elsif b.text != blank -%}
                <p class="lib-shipping__detail">{{ b.text }}</p>
              {%- endif -%}

              {%- if b.link_label != blank and b.link_url != blank -%}
                <div class="lib-shipping__link">
                  {%- render 'lib-button', label: b.link_label, url: b.link_url, style: 'text', new_tab: b.link_new_tab, arrow: false -%}
                </div>
              {%- endif -%}
            </div>
          </li>
        {%- endfor -%}
      </ul>
    {%- endif -%}

    {%- if policy_links -%}
      <ul class="lib-shipping__policies" role="list">
        {%- if shop.shipping_policy != blank and s.shipping_policy_label != blank -%}
          <li><a href="{{ shop.shipping_policy.url }}">{{ s.shipping_policy_label | escape }}</a></li>
        {%- endif -%}
        {%- if shop.refund_policy != blank and s.refund_policy_label != blank -%}
          <li><a href="{{ shop.refund_policy.url }}">{{ s.refund_policy_label | escape }}</a></li>
        {%- endif -%}
      </ul>
    {%- endif -%}
  </div>
</div>

{% stylesheet %}
  .lib-shipping {
    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-shipping__inner { max-width: 1200px; margin: 0 auto; }
  .lib-shipping--full .lib-shipping__inner { max-width: none; }
  .lib-shipping[data-layout='list'] .lib-shipping__inner { max-width: 40rem; margin-inline: 0; }
  @media (max-width: 749px) {
    .lib-shipping { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-shipping[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-shipping[data-show='mobile'] { display: none; }
  }

  .lib-shipping__header { margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
  .lib-shipping[data-layout='list'] .lib-shipping__header { margin-bottom: 1.25rem; }
  .lib-shipping__heading {
    margin: 0;
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .lib-shipping__heading--small { font-size: clamp(1.125rem, 1.6vw, 1.375rem); }
  .lib-shipping__heading--large { font-size: clamp(2rem, 4vw, 3.25rem); }
  .lib-shipping__text { margin-top: 0.75rem; font-size: 1rem; line-height: 1.6; opacity: 0.8; }
  .lib-shipping__text > :first-child { margin-top: 0; }
  .lib-shipping__text > :last-child { margin-bottom: 0; }

  .lib-shipping__list {
    display: grid;
    grid-template-columns: repeat(var(--lib-cols), minmax(0, 1fr));
    gap: clamp(1rem, 2.5vw, 2rem);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .lib-shipping[data-layout='list'] .lib-shipping__list { grid-template-columns: 1fr; gap: 1rem; }
  @media (max-width: 749px) {
    .lib-shipping__list { grid-template-columns: repeat(var(--lib-cols-m), minmax(0, 1fr)); }
  }
  .lib-shipping__item { display: flex; flex-direction: column; gap: 0.875rem; min-width: 0; }
  .lib-shipping[data-layout='list'] .lib-shipping__item { flex-direction: row; align-items: flex-start; gap: 0.875rem; }
  .lib-shipping[data-style='card'] .lib-shipping__item {
    padding: clamp(1.125rem, 2.5vw, 1.75rem);
    border-radius: 14px;
    background: var(--lib-card);
    box-shadow: inset 0 0 0 1px var(--lib-border);
  }
  .lib-shipping[data-style='divided'][data-layout='list'] .lib-shipping__item + .lib-shipping__item {
    padding-top: 1rem;
    border-top: 1px solid var(--lib-border);
  }

  .lib-shipping__icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--lib-accent) 12%, transparent);
    color: var(--lib-accent);
  }
  .lib-shipping[data-layout='list'] .lib-shipping__icon { width: 2.25rem; height: 2.25rem; }
  .lib-shipping__icon svg { width: 1.375rem; height: 1.375rem; }
  .lib-shipping[data-layout='list'] .lib-shipping__icon svg { width: 1.125rem; height: 1.125rem; }
  .lib-shipping__icon-img { display: block; width: 1.375rem; height: 1.375rem; object-fit: contain; }

  .lib-shipping__body { min-width: 0; }
  .lib-shipping__title { margin: 0; font-weight: 600; font-size: 1.0625rem; line-height: 1.35; }
  .lib-shipping[data-layout='list'] .lib-shipping__title { font-size: 0.9375rem; }
  .lib-shipping__detail { margin: 0.35rem 0 0; font-size: 0.9375rem; line-height: 1.55; opacity: 0.8; }
  .lib-shipping[data-layout='list'] .lib-shipping__detail { font-size: 0.875rem; margin-top: 0.2rem; }
  .lib-shipping__detail a { color: inherit; text-underline-offset: 0.2em; }
  .lib-shipping__estimate strong { font-weight: 600; opacity: 1; }
  .lib-shipping__link { margin-top: 0.6rem; font-size: 0.9375rem; }

  .lib-shipping__policies {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    margin: clamp(1.5rem, 3vw, 2.25rem) 0 0;
    padding: 0;
    list-style: none;
    font-size: 0.9375rem;
  }
  .lib-shipping__policies a {
    display: inline-block;
    padding-block: 0.25rem;
    color: inherit;
    text-underline-offset: 0.25em;
  }
  .lib-shipping__policies a:focus-visible,
  .lib-shipping__detail a:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 3px; }

  /* Alignment applies to the header, the row layout's items and the policy links. */
  .lib-shipping__inner[data-align='center'] .lib-shipping__header { text-align: center; }
  .lib-shipping__inner[data-align='center'] .lib-shipping__policies { justify-content: center; }
  .lib-shipping[data-layout='row'] .lib-shipping__inner[data-align='center'] .lib-shipping__item { align-items: center; text-align: center; }
  @media (max-width: 749px) {
    .lib-shipping__inner[data-align-m='left'] .lib-shipping__header { text-align: left; }
    .lib-shipping__inner[data-align-m='left'] .lib-shipping__policies { justify-content: flex-start; }
    .lib-shipping[data-layout='row'] .lib-shipping__inner[data-align-m='left'] .lib-shipping__item { align-items: flex-start; text-align: left; }
    .lib-shipping__inner[data-align-m='center'] .lib-shipping__header { text-align: center; }
    .lib-shipping__inner[data-align-m='center'] .lib-shipping__policies { justify-content: center; }
    .lib-shipping[data-layout='row'] .lib-shipping__inner[data-align-m='center'] .lib-shipping__item { align-items: center; text-align: center; }
  }

  /* Motion: items rise in one after another as they scroll into view. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .lib-shipping[data-animate] .lib-shipping__item {
        animation: lib-shipping-rise linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 60%;
      }
    }
  }
  @keyframes lib-shipping-rise {
    from { opacity: 0; transform: translateY(18px); }
  }
{% endstylesheet %}

{% javascript %}
  (() => {
    const DAY_MS = 86400000;
    const isWeekend = (date) => date.getDay() === 0 || date.getDay() === 6;
    // Counts forward from today; business days skip Saturdays and Sundays.
    const addDays = (start, days, business) => {
      const date = new Date(start.getTime());
      let left = days;
      while (left > 0) {
        date.setTime(date.getTime() + DAY_MS);
        if (!business || !isWeekend(date)) left -= 1;
      }
      return date;
    };
    const fill = (root) => {
      root.querySelectorAll('.lib-shipping__estimate[data-template]').forEach((el) => {
        const min = Number(el.dataset.min);
        const max = Math.max(min, Number(el.dataset.max));
        if (!Number.isFinite(min) || !el.dataset.template.includes('{dates}')) return;
        const business = el.dataset.business === 'true';
        const today = new Date();
        today.setHours(12, 0, 0, 0);
        let format;
        try {
          format = new Intl.DateTimeFormat(document.documentElement.lang || undefined, {weekday: 'short', month: 'short', day: 'numeric'});
        } catch (error) {
          // An unknown page language falls back to the browser's.
          format = new Intl.DateTimeFormat(undefined, {weekday: 'short', month: 'short', day: 'numeric'});
        }
        const from = format.format(addDays(today, min, business));
        const to = format.format(addDays(today, max, business));
        const dates = document.createElement('strong');
        dates.textContent = min === max ? from : `${from} – ${to}`;
        const [before, after] = el.dataset.template.split('{dates}');
        el.replaceChildren(before, dates, after ?? '');
      });
    };
    fill(document);
    document.addEventListener('shopify:section:load', (event) => fill(event.target));
  })();
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Shipping & returns",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 8,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Delivery and returns" },
    {
      "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": "header", "content": "Store policies" },
    { "type": "checkbox", "id": "show_policy_links", "label": "Link to the store's shipping and refund policies", "info": "Shown only for policies filled in under Settings > Policies.", "default": true },
    { "type": "text", "id": "shipping_policy_label", "label": "Shipping policy link text", "default": "Shipping policy" },
    { "type": "text", "id": "refund_policy_label", "label": "Refund policy link text", "default": "Returns and refunds" },

    { "type": "header", "content": "Layout" },
    {
      "type": "select",
      "id": "layout",
      "label": "Layout",
      "info": "Row suits home and landing pages; list suits the area under a product's buy box.",
      "options": [
        { "value": "row", "label": "Row" },
        { "value": "list", "label": "List" }
      ],
      "default": "row"
    },
    {
      "type": "select",
      "id": "item_style",
      "label": "Item style",
      "options": [
        { "value": "plain", "label": "Plain" },
        { "value": "card", "label": "Card" },
        { "value": "divided", "label": "Divided (list only)" }
      ],
      "default": "plain"
    },
    { "type": "range", "id": "columns", "label": "Columns on desktop", "min": 1, "max": 4, "step": 1, "default": 4, "visible_if": "{{ section.settings.layout == 'row' }}" },
    { "type": "range", "id": "columns_mobile", "label": "Columns on mobile", "min": 1, "max": 2, "step": 1, "default": 1, "visible_if": "{{ section.settings.layout == 'row' }}" },
    {
      "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": "Alignment on desktop",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" }
      ],
      "default": "left"
    },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "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_accent", "label": "Accent (icons)", "default": "#7A5A3A" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" },
    { "type": "color", "id": "color_card", "label": "Card background", "default": "#FAF6F1" },
    { "type": "color", "id": "color_border", "label": "Borders", "default": "#E7E0D8" },

    { "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": 64 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 64 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 40 },
    { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 40 },
    {
      "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": "Fade in on scroll", "info": "Only in browsers that support scroll-driven animations; never when reduced motion is on.", "default": false }
  ],
  "blocks": [
    {
      "type": "item",
      "name": "Point",
      "settings": [
        {
          "type": "select",
          "id": "icon",
          "label": "Icon",
          "options": [
            { "value": "truck", "label": "Delivery" },
            { "value": "return", "label": "Returns" },
            { "value": "clock", "label": "Clock" },
            { "value": "calendar", "label": "Calendar" },
            { "value": "globe", "label": "International" },
            { "value": "box", "label": "Package" },
            { "value": "store", "label": "Store pickup" },
            { "value": "shield", "label": "Protection" },
            { "value": "card", "label": "Payment" },
            { "value": "gift", "label": "Gift" },
            { "value": "leaf", "label": "Leaf" }
          ],
          "default": "truck"
        },
        { "type": "image_picker", "id": "icon_image", "label": "Custom icon", "info": "Optional. Replaces the icon above." },
        { "type": "text", "id": "title", "label": "Title", "default": "Free delivery over $75" },
        { "type": "inline_richtext", "id": "text", "label": "Text", "default": "Tracked delivery in 2 to 4 business days." },
        { "type": "text", "id": "link_label", "label": "Link text" },
        { "type": "url", "id": "link_url", "label": "Link" },
        { "type": "checkbox", "id": "link_new_tab", "label": "Open link in a new tab", "default": false }
      ]
    },
    {
      "type": "estimate",
      "name": "Delivery estimate",
      "limit": 1,
      "settings": [
        { "type": "paragraph", "content": "Shows dates counted from the shopper's today, in their language. Use your real handling plus transit times." },
        {
          "type": "select",
          "id": "icon",
          "label": "Icon",
          "options": [
            { "value": "calendar", "label": "Calendar" },
            { "value": "truck", "label": "Delivery" },
            { "value": "clock", "label": "Clock" },
            { "value": "box", "label": "Package" }
          ],
          "default": "calendar"
        },
        { "type": "image_picker", "id": "icon_image", "label": "Custom icon", "info": "Optional. Replaces the icon above." },
        { "type": "text", "id": "title", "label": "Title", "default": "Estimated delivery" },
        { "type": "range", "id": "min_days", "label": "Earliest delivery, days from today", "min": 0, "max": 30, "step": 1, "default": 3 },
        { "type": "range", "id": "max_days", "label": "Latest delivery, days from today", "min": 0, "max": 45, "step": 1, "default": 5 },
        { "type": "checkbox", "id": "business_days", "label": "Count business days only (skip weekends)", "default": true },
        { "type": "text", "id": "estimate_text", "label": "Estimate text", "info": "{dates} becomes the date range.", "default": "Order today, arrives {dates}" },
        { "type": "text", "id": "fallback_text", "label": "Text without dates", "info": "Shown until the dates load, and if they can't.", "default": "Arrives in 3 to 5 business days" },
        { "type": "text", "id": "link_label", "label": "Link text" },
        { "type": "url", "id": "link_url", "label": "Link" },
        { "type": "checkbox", "id": "link_new_tab", "label": "Open link in a new tab", "default": false }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Shipping & returns",
      "blocks": [
        { "type": "item", "settings": { "icon": "truck", "title": "Free delivery over $75", "text": "Tracked delivery in 2 to 4 business days." } },
        { "type": "estimate" },
        { "type": "item", "settings": { "icon": "return", "title": "30-day returns", "text": "Changed your mind? Send it back unused within 30 days." } },
        { "type": "item", "settings": { "icon": "globe", "title": "We ship worldwide", "text": "Duties and taxes are shown at checkout." } }
      ]
    }
  ]
}
{% endschema %}
```

### snippets/lib-button.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{% doc %}
  Animated call-to-action link. Light sweep on hover, arrow slides right.
  Colors come from the parent section's --lib-accent / --lib-accent-text / --lib-text vars.
  Inspired by Uiverse.io button patterns; rewritten for themes, no dependencies.

  @param {string} label - Button text
  @param {string} [url] - Link target; renders a disabled-looking span when blank
  @param {string} [style] - 'primary' (default), 'outline' or 'text'
  @param {boolean} [new_tab] - Open the link in a new tab
  @param {boolean} [arrow] - Show the trailing arrow (default true)

  @example
  {% render 'lib-button', label: section.settings.button_label, url: section.settings.button_url, style: 'outline' %}
{% enddoc %}

{%- liquid
  assign btn_style = style | default: 'primary'
  assign show_arrow = true
  if arrow == false
    assign show_arrow = false
  endif
-%}

{%- unless label == blank -%}
{%- capture inner -%}
  <span class="lib-btn__label">{{ label | escape }}</span>
  {%- if show_arrow -%}
    <svg class="lib-btn__arrow" 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>
  {%- endif -%}
{%- endcapture -%}

{%- if url != blank -%}
  <a
    class="lib-btn lib-btn--{{ btn_style }}"
    href="{{ url }}"
    {% if new_tab %}
      target="_blank" rel="noopener"
    {% endif %}
  >
    {{- inner -}}
  </a>
{%- else -%}
  <span class="lib-btn lib-btn--{{ btn_style }}" aria-disabled="true">{{ inner }}</span>
{%- endif -%}
{%- endunless -%}

{% stylesheet %}
  .lib-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    padding: 0.85em 1.5em;
    border-radius: var(--lib-radius, 999px);
    font: inherit;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    overflow: hidden;
    isolation: isolate;
    transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
  }
  .lib-btn--primary {
    background: var(--lib-accent, #111);
    color: var(--lib-accent-text, #fff);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.12);
  }
  .lib-btn--outline {
    background: transparent;
    color: var(--lib-text, #111);
    box-shadow: inset 0 0 0 1.5px currentColor;
  }
  .lib-btn--text {
    padding-inline: 0;
    background: none;
    color: var(--lib-text, #111);
    text-decoration: underline;
    text-underline-offset: 0.3em;
    overflow: visible;
  }
  .lib-btn--text::before { display: none; }
  .lib-btn--text:hover { transform: none; box-shadow: none; }
  .lib-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(110deg, transparent 30%, rgb(255 255 255 / 0.35) 50%, transparent 70%);
    transform: translateX(-120%);
    transition: transform 0.6s ease;
  }
  .lib-btn--outline::before {
    background: linear-gradient(110deg, transparent 30%, rgb(127 127 127 / 0.18) 50%, transparent 70%);
  }
  .lib-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px -8px rgb(0 0 0 / 0.35);
  }
  .lib-btn:hover::before { transform: translateX(120%); }
  .lib-btn__arrow { transition: transform 0.2s ease; }
  .lib-btn:hover .lib-btn__arrow { transform: translateX(3px); }
  .lib-btn:focus-visible {
    outline: 2px solid var(--lib-accent, #111);
    outline-offset: 3px;
  }
  .lib-btn[aria-disabled='true'] { pointer-events: none; }
  @media (prefers-reduced-motion: reduce) {
    .lib-btn, .lib-btn::before, .lib-btn__arrow { transition: none; }
    .lib-btn:hover { transform: none; }
  }
{% 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.
