# Rich text: free Shopify section

> Eyebrow, headings, text and buttons as blocks in any order, at three reading widths, aligned per device, on a color, gradient or image with an overlay.

Rich text is a free Shopify section for a heading, a few paragraphs and buttons on their own: a brand statement, a care note or a short collection intro. Eyebrow, heading, text and buttons are blocks you can reorder, the text keeps a narrow, medium or wide reading width, alignment is set per device, and the background can be a color, a gradient or a desktop and mobile image under an overlay. It has no JavaScript and works in Online Store 2.0 themes without an app.

- **Category:** Content and storytelling
- **Use on:** Home, About, Collection, Landing pages, Any page
- **Reads:** Content only
- **Size:** 22 KB in 3 files, no JavaScript
- **License:** MIT, © 2026 Lintel
- **Page:** https://lintelagency.com/resources/shopify-sections/rich-text
- **Live demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-rich-text.html
- **Added:** 2026-11-02

## What it’s for

- A short brand statement or mission line between image sections on the home page
- An introduction above a collection grid or at the top of an About page
- A campaign message on a background photo with a button to the sale

## Editor settings

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

### Layout

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `content_width` | `select` | Text width | narrow | `narrow`, `medium`, `wide` |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `text_alignment` | Text alignment on desktop | center |  |
| `text_alignment_mobile` | `select` | Text alignment on mobile | center | `left`, `center`, `right` |

### Background image

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `image` | `image_picker` | Desktop image | – |  |
| `image_mobile` | `image_picker` | Mobile image | – |  |
| `overlay_color` | `color` | Overlay | #1C1917 |  |
| `overlay_opacity` | `range` | Overlay opacity | `45` | 0–90 %, step 5 |
| `corner_radius` | `range` | Panel corner radius | `16` | 0–40 px, step 2 |

### Colors

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #F6F1EA |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #231F1B |  |
| `color_accent` | `color` | Accent (solid button, eyebrow) | #7A5A3A |  |
| `color_accent_text` | `color` | Text on accent | #FFFFFF |  |

### Spacing and visibility

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `padding_top` | `range` | Top padding on desktop | `96` | 0–200 px, step 4 |
| `padding_bottom` | `range` | Bottom padding on desktop | `96` | 0–200 px, step 4 |
| `padding_top_mobile` | `range` | Top padding on mobile | `56` | 0–160 px, step 4 |
| `padding_bottom_mobile` | `range` | Bottom padding on mobile | `56` | 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.

### Eyebrow (`eyebrow`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `text` | `text` | Text | Our promise |  |

### Heading (`heading`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `heading` | `inline_richtext` | Heading | `Made to be <em>used</em>, every day` |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | large | `small`, `medium`, `large`, `xlarge` |

### Text (`text`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `text` | `richtext` | Text | `<p>Share your brand's story, a care note or a short introduction to a collection. Text keeps a comfortable reading width on every screen.</p>` |  |
| `text_size` | `select` | Text size | medium | `small`, `medium`, `large` |

### Buttons (`buttons`, up to 2)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `button_label` | `text` | First button label | Shop the collection |  |
| `button_url` | `url` | First button link | – |  |
| `button_style` | `select` | First button style | primary | `primary`, `outline`, `text` |
| `button_new_tab` | `checkbox` | Open first link in a new tab | `false` |  |
| `button2_label` | `text` | Second button label | – |  |
| `button2_url` | `url` | Second button link | – |  |
| `button2_style` | `select` | Second button style | text | `primary`, `outline`, `text` |
| `button2_new_tab` | `checkbox` | Open second 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-rich-text.liquid` and paste the contents of `sections/lib-rich-text.liquid` from the source below. Save.
4. In the `snippets` folder, add `lib-image.liquid` and `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 · Rich text**. 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-rich-text.liquid https://lintelagency.com/resources/shopify-sections/files/lib-rich-text.liquid
curl -fsSL -o snippets/lib-image.liquid https://lintelagency.com/resources/shopify-sections/files/lib-image.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-rich-text.liquid --only snippets/lib-image.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-rich-text.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-rich-text.liquid): goes in `sections/`
- [snippets/lib-image.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid): goes in `snippets/`
- [snippets/lib-button.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid): goes in `snippets/`

## Questions

### How is it different from my theme's own rich text?

It adds what theme versions often lack: a heading tag and size per heading block (H1 to H3 or a paragraph), three reading widths, text alignment set separately for desktop and mobile, an optional desktop and mobile background image with focal points and an overlay, and per-device padding, visibility and an anchor ID.

### Can I use it as the page's H1?

Yes. Set the heading block's tag to H1 when this section holds the page's main heading, for example on a landing page built from sections. Keep one H1 per page; leave it on H2 everywhere else.

### How do I keep text readable on a background photo?

Raise the overlay opacity (0 to 90 percent, in any color) and set a light text color. The image sits behind the text in a framed panel, so the copy never runs over the busiest part without the overlay.

### Does it slow the page down?

No. It ships no JavaScript. The optional fade-in uses CSS scroll-driven animation in browsers that support it and is skipped under reduced motion. A background image loads eagerly only when the section is one of the first two on the page.

## Source

### sections/lib-rich-text.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Rich text: eyebrow, headings, text and buttons as blocks in any order, centred or aligned per
  device, at a narrow, medium or wide measure. Plain background, gradient, or a desktop and mobile
  background image (each with its focal point) under a colour overlay. No JavaScript.
  Depends on: snippets/lib-image.liquid, snippets/lib-button.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign eager = false
  if section.index <= 2
    assign eager = true
  endif
  assign has_image = false
  if s.image != blank or s.image_mobile != blank
    assign has_image = true
  endif
-%}

<div
  class="lib-rich-text lib-rich-text--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-measure="{{ s.content_width }}"
  {% if has_image %}
    data-image
  {% endif %}
  {% 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-overlay: {{ s.overlay_color }};
    --lib-overlay-opacity: {{ s.overlay_opacity | divided_by: 100.0 }};
    --lib-radius: {{ s.corner_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-rich-text__frame">
    {%- if has_image -%}
      <div class="lib-rich-text__media" aria-hidden="true">
        {%- render 'lib-image',
          image: s.image,
          image_mobile: s.image_mobile,
          class: 'lib-rich-text__img',
          sizes: '100vw',
          eager: eager
        -%}
      </div>
    {%- endif -%}

    <div class="lib-rich-text__content" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
      {%- for block in section.blocks -%}
        {%- assign b = block.settings -%}
        {%- case block.type -%}
          {%- when 'eyebrow' -%}
            {%- if b.text != blank -%}
              <p class="lib-rich-text__eyebrow" {{ block.shopify_attributes }}>{{ b.text | escape }}</p>
            {%- endif -%}
          {%- when 'heading' -%}
            {%- if b.heading != blank -%}
              {%- assign tag = b.heading_tag | default: 'h2' -%}
              <{{ tag }} class="lib-rich-text__heading lib-rich-text__heading--{{ b.heading_size }}" {{ block.shopify_attributes }}>
                {{- b.heading -}}
              </{{ tag }}>
            {%- endif -%}
          {%- when 'text' -%}
            {%- if b.text != blank -%}
              <div class="lib-rich-text__text lib-rich-text__text--{{ b.text_size }}" {{ block.shopify_attributes }}>{{ b.text }}</div>
            {%- endif -%}
          {%- when 'buttons' -%}
            {%- if b.button_label != blank or b.button2_label != blank -%}
              <div class="lib-rich-text__actions" {{ block.shopify_attributes }}>
                {%- render 'lib-button',
                  label: b.button_label,
                  url: b.button_url,
                  style: b.button_style,
                  new_tab: b.button_new_tab
                -%}
                {%- render 'lib-button',
                  label: b.button2_label,
                  url: b.button2_url,
                  style: b.button2_style,
                  new_tab: b.button2_new_tab,
                  arrow: false
                -%}
              </div>
            {%- endif -%}
        {%- endcase -%}
      {%- endfor -%}
    </div>
  </div>
</div>

{% stylesheet %}
  .lib-rich-text {
    color: var(--lib-text);
    padding: var(--lib-pt) 1rem var(--lib-pb);
  }
  .lib-rich-text__frame {
    position: relative;
    isolation: isolate;
    max-width: 1200px;
    margin: 0 auto;
    border-radius: var(--lib-radius);
    overflow: hidden;
    background-color: var(--lib-bg);
    background-image: var(--lib-bg-image, none);
  }
  .lib-rich-text--full { padding-inline: 0; }
  .lib-rich-text--full .lib-rich-text__frame { max-width: none; border-radius: 0; }
  /* Without an image the background belongs to the whole section, edge to edge. */
  .lib-rich-text:not([data-image]) {
    background-color: var(--lib-bg);
    background-image: var(--lib-bg-image, none);
  }
  .lib-rich-text:not([data-image]) .lib-rich-text__frame { background: none; overflow: visible; }
  .lib-rich-text[data-image] .lib-rich-text__frame { padding: clamp(3rem, 8vw, 7rem) clamp(1.25rem, 5vw, 4rem); }

  .lib-rich-text__media { position: absolute; inset: 0; z-index: -1; }
  .lib-rich-text__media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--lib-overlay);
    opacity: var(--lib-overlay-opacity);
  }
  .lib-rich-text__img { display: block; width: 100%; height: 100%; object-fit: cover; }

  .lib-rich-text__content {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    max-width: 40rem;
    margin: 0 auto;
    text-align: center;
  }
  .lib-rich-text[data-measure='medium'] .lib-rich-text__content { max-width: 52rem; }
  .lib-rich-text[data-measure='wide'] .lib-rich-text__content { max-width: 68rem; }
  .lib-rich-text__content[data-align='left'] { align-items: flex-start; text-align: left; margin-inline: 0 auto; }
  .lib-rich-text__content[data-align='right'] { align-items: flex-end; text-align: right; margin-inline: auto 0; }
  @media (max-width: 749px) {
    .lib-rich-text { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-rich-text[data-show='desktop'] { display: none; }
    .lib-rich-text__content[data-align-m='left'] { align-items: flex-start; text-align: left; }
    .lib-rich-text__content[data-align-m='center'] { align-items: center; text-align: center; }
    .lib-rich-text__content[data-align-m='right'] { align-items: flex-end; text-align: right; }
  }
  @media (min-width: 750px) {
    .lib-rich-text[data-show='mobile'] { display: none; }
  }

  .lib-rich-text__content > * { margin: 0; }
  .lib-rich-text__content > * + * { margin-top: 1.25rem; }
  .lib-rich-text__content > .lib-rich-text__eyebrow + * { margin-top: 0.875rem; }
  .lib-rich-text__content > .lib-rich-text__actions { margin-top: 2rem; }
  .lib-rich-text__eyebrow {
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--lib-accent);
  }
  .lib-rich-text[data-image] .lib-rich-text__eyebrow { color: inherit; opacity: 0.85; }
  .lib-rich-text__heading {
    font-size: clamp(2rem, 4vw, 3.25rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
    text-wrap: balance;
  }
  .lib-rich-text__heading--small { font-size: clamp(1.5rem, 2.5vw, 2rem); }
  .lib-rich-text__heading--medium { font-size: clamp(1.75rem, 3vw, 2.5rem); }
  .lib-rich-text__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); }
  .lib-rich-text__text { font-size: 1.0625rem; line-height: 1.7; text-wrap: pretty; }
  .lib-rich-text__text--small { font-size: 0.9375rem; }
  .lib-rich-text__text--large { font-size: clamp(1.125rem, 1.6vw, 1.3125rem); line-height: 1.6; }
  .lib-rich-text__text > :first-child { margin-top: 0; }
  .lib-rich-text__text > :last-child { margin-bottom: 0; }
  .lib-rich-text__text a { color: inherit; text-underline-offset: 0.2em; }
  .lib-rich-text__actions { display: flex; flex-wrap: wrap; gap: 0.75rem 1.25rem; justify-content: center; }
  .lib-rich-text__content[data-align='left'] .lib-rich-text__actions { justify-content: flex-start; }
  .lib-rich-text__content[data-align='right'] .lib-rich-text__actions { justify-content: flex-end; }
  @media (max-width: 749px) {
    .lib-rich-text__content[data-align-m='left'] .lib-rich-text__actions { justify-content: flex-start; }
    .lib-rich-text__content[data-align-m='center'] .lib-rich-text__actions { justify-content: center; }
    .lib-rich-text__content[data-align-m='right'] .lib-rich-text__actions { justify-content: flex-end; }
  }

  /* Motion: blocks rise in as the section scrolls into view (scroll-driven animations only). */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .lib-rich-text[data-animate] .lib-rich-text__content > * {
        animation: lib-rich-text-rise linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 55%;
      }
    }
  }
  @keyframes lib-rich-text-rise {
    from { opacity: 0; transform: translateY(20px); }
  }
{% endstylesheet %}

{% schema %}
{
  "name": "Lib · Rich text",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 8,
  "settings": [
    { "type": "header", "content": "Layout" },
    {
      "type": "select",
      "id": "content_width",
      "label": "Text width",
      "options": [
        { "value": "narrow", "label": "Narrow" },
        { "value": "medium", "label": "Medium" },
        { "value": "wide", "label": "Wide" }
      ],
      "default": "narrow"
    },
    {
      "type": "select",
      "id": "section_width",
      "label": "Width",
      "options": [
        { "value": "page", "label": "Page width" },
        { "value": "full", "label": "Full width" }
      ],
      "default": "page"
    },
    { "type": "text_alignment", "id": "text_alignment", "label": "Text alignment on desktop", "default": "center" },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Text alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" },
        { "value": "right", "label": "Right" }
      ],
      "default": "center"
    },

    { "type": "header", "content": "Background image" },
    { "type": "paragraph", "content": "Optional. With an image the text sits on it inside a framed panel." },
    { "type": "image_picker", "id": "image", "label": "Desktop image" },
    { "type": "image_picker", "id": "image_mobile", "label": "Mobile image", "info": "Optional. Shown below 750px." },
    { "type": "color", "id": "overlay_color", "label": "Overlay", "default": "#1C1917" },
    { "type": "range", "id": "overlay_opacity", "label": "Overlay opacity", "min": 0, "max": 90, "step": 5, "unit": "%", "default": 45 },
    { "type": "range", "id": "corner_radius", "label": "Panel corner radius", "info": "Page width only.", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 16 },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#F6F1EA" },
    { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." },
    { "type": "color", "id": "color_text", "label": "Text", "info": "Use a light text color on a dark image.", "default": "#231F1B" },
    { "type": "color", "id": "color_accent", "label": "Accent (solid button, eyebrow)", "default": "#7A5A3A" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" },

    { "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": 96 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 96 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 56 },
    { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 56 },
    {
      "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": "eyebrow",
      "name": "Eyebrow",
      "settings": [
        { "type": "text", "id": "text", "label": "Text", "default": "Our promise" }
      ]
    },
    {
      "type": "heading",
      "name": "Heading",
      "settings": [
        { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Made to be <em>used</em>, every day" },
        {
          "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" },
            { "value": "xlarge", "label": "Extra large" }
          ],
          "default": "large"
        }
      ]
    },
    {
      "type": "text",
      "name": "Text",
      "settings": [
        { "type": "richtext", "id": "text", "label": "Text", "default": "<p>Share your brand's story, a care note or a short introduction to a collection. Text keeps a comfortable reading width on every screen.</p>" },
        {
          "type": "select",
          "id": "text_size",
          "label": "Text size",
          "options": [
            { "value": "small", "label": "Small" },
            { "value": "medium", "label": "Medium" },
            { "value": "large", "label": "Large" }
          ],
          "default": "medium"
        }
      ]
    },
    {
      "type": "buttons",
      "name": "Buttons",
      "limit": 2,
      "settings": [
        { "type": "text", "id": "button_label", "label": "First button label", "default": "Shop the collection" },
        { "type": "url", "id": "button_url", "label": "First button link" },
        {
          "type": "select",
          "id": "button_style",
          "label": "First button style",
          "options": [
            { "value": "primary", "label": "Solid" },
            { "value": "outline", "label": "Outline" },
            { "value": "text", "label": "Text link" }
          ],
          "default": "primary"
        },
        { "type": "checkbox", "id": "button_new_tab", "label": "Open first link in a new tab", "default": false },
        { "type": "text", "id": "button2_label", "label": "Second button label" },
        { "type": "url", "id": "button2_url", "label": "Second button link" },
        {
          "type": "select",
          "id": "button2_style",
          "label": "Second button style",
          "options": [
            { "value": "primary", "label": "Solid" },
            { "value": "outline", "label": "Outline" },
            { "value": "text", "label": "Text link" }
          ],
          "default": "text"
        },
        { "type": "checkbox", "id": "button2_new_tab", "label": "Open second link in a new tab", "default": false }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Rich text",
      "blocks": [
        { "type": "eyebrow" },
        { "type": "heading" },
        { "type": "text" },
        { "type": "buttons" }
      ]
    }
  ]
}
{% 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 %}
```

### 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.
