# Comparison table: free Shopify section

> Compare 2 to 4 products or plans by feature rows with check, cross and text cells, a highlighted column and a sticky feature column on mobile.

Comparison table is a free Shopify section that puts two to four products or plans side by side. Each column can take a product, which supplies its image, title, price and link, or your own image, title and price text; each row holds a feature and one value per column, where yes, no and a dash become icons. It is a real HTML table with a sticky feature column on small screens, needs no JavaScript and works in Online Store 2.0 themes without an app.

- **Category:** Product page
- **Use on:** Product, Collection, Landing pages, Any page
- **Reads:** Products (optional)
- **Size:** 29 KB in 2 files, no JavaScript
- **License:** MIT, © 2026 Lintel
- **Page:** https://lintelagency.com/resources/shopify-sections/comparison-table
- **Live demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-comparison-table.html
- **Added:** 2026-11-02

## What it’s for

- Helping shoppers pick between product sizes, bundles or sets on a collection or landing page
- Comparing subscription or service plans with your own price text
- Showing what a premium version adds over the standard one on a product page

## Editor settings

In the theme editor the section is called “Lib · Comparison table”. 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 | Which set is right for you? |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large`, `xlarge` |
| `text` | `richtext` | Text | – |  |

### Table

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `show_images` | `checkbox` | Show column images | `true` |  |
| `image_ratio` | `select` | Image shape | square | `square`, `portrait`, `landscape` |
| `show_prices` | `checkbox` | Show prices | `true` |  |
| `corner_label` | `text` | Feature column heading | – |  |
| `caption` | `text` | Table name (screen readers) | – |  |
| `scroll_label` | `text` | Table area name (screen readers) | Comparison table, scrolls sideways on small screens |  |
| `yes_label` | `text` | Yes icon text (screen readers) | Included |  |
| `no_label` | `text` | No icon text (screen readers) | Not included |  |
| `none_label` | `text` | Dash text (screen readers) | Not applicable |  |
| `from_label` | `text` | Price prefix when variants differ | From |  |
| `sale_label` | `text` | Sale price (screen readers) | Sale price |  |
| `regular_label` | `text` | Regular price (screen readers) | Regular price |  |

### Layout

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `text_alignment` | Heading alignment on desktop | center |  |
| `text_alignment_mobile` | `select` | Heading alignment on mobile | left | `left`, `center`, `right` |

### 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, group headings) | #7A5A3A |  |
| `color_accent_text` | `color` | Text on accent | #FFFFFF |  |
| `color_panel` | `color` | Table background | #FFFFFF |  |
| `color_line` | `color` | Lines | #E7E0D6 |  |
| `color_highlight` | `color` | Highlighted column | #7A5A3A |  |
| `color_highlight_text` | `color` | Highlight label text | #FFFFFF |  |
| `color_yes` | `color` | Yes icon | #3F6B45 |  |
| `color_no` | `color` | No icon | #9A3B2E |  |

### Spacing and visibility

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

## Blocks

Up to 40 blocks in total.

### Column (`column`, up to 4)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `product` | `product` | Product | – |  |
| `image` | `image_picker` | Image | – |  |
| `title` | `text` | Title | Product |  |
| `price_text` | `text` | Price text | – |  |
| `highlight` | `checkbox` | Highlight this column | `false` |  |
| `highlight_label` | `text` | Highlight label | – |  |
| `button_label` | `text` | Button label | View product |  |
| `button_url` | `url` | Button link | – |  |
| `button_style` | `select` | Button style | outline | `primary`, `outline`, `text` |
| `button_new_tab` | `checkbox` | Open link in a new tab | `false` |  |

### Row group heading (`group`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `title` | `text` | Heading | What's included |  |

### Row (`row`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `feature` | `text` | Feature | Feature |  |
| `note` | `text` | Note | – |  |
| `values` | `text` | Values | yes \| yes \| no |  |

## 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-comparison-table.liquid` and paste the contents of `sections/lib-comparison-table.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 · Comparison table**. 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-comparison-table.liquid https://lintelagency.com/resources/shopify-sections/files/lib-comparison-table.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-comparison-table.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-comparison-table.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-comparison-table.liquid): goes in `sections/`
- [snippets/lib-button.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid): goes in `snippets/`

## Questions

### How do I fill in the rows?

Add one Column block per product or plan, then a Row block per feature. In a row, type one value per column in the same order, separated by a vertical bar (|), for example "yes | yes | no" or "2 | 4 | 6". yes, no and a dash (-) become a check, a cross and a dash; anything else shows as typed. Row group heading blocks split the table into labelled groups.

### Does it read prices from my products?

Yes. Pick a product in a column and the section shows its featured image, title and price, with the compare-at price struck through when it is on sale and a "From" prefix when variant prices differ. The button links to the product unless you set another link. Price text in the column replaces the product price, which is useful for plans.

### How does it work on mobile?

The table keeps its rows and columns and scrolls sideways inside its own area, which is labelled for screen readers and reachable with the keyboard. The feature column stays fixed on the left while the products scroll, and the page itself never scrolls sideways.

### Is it accessible?

It is a real table with column and row headers, so screen readers announce the product and feature for each cell. Check, cross and dash icons have screen-reader text you set, such as Included and Not included, and the sale and regular prices are labelled.

### Can I highlight one column?

Yes. Turn on Highlight this column and add your own label, for example "Our pick". The column gets a tinted background, the label sits above it and an outline button becomes solid. The label is your own words; the section never adds ratings or popularity claims.

## Source

### sections/lib-comparison-table.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Comparison table: two to four products or plans side by side. Column blocks take a product
  (image, title, price and link come from it) or a custom image, title and price; one column can
  be highlighted with the merchant's own label. Row blocks hold a feature and one value per column,
  separated by |: yes / no / dash values become icons with screen-reader text, anything else
  shows as typed. Group blocks add a heading row. A real <table>; on small screens it scrolls
  sideways inside its own region while the feature column stays put. No JavaScript.
  Depends on: snippets/lib-button.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign columns = section.blocks | where: 'type', 'column'
  assign column_count = columns.size
  assign eager = false
  if section.index <= 2
    assign eager = true
  endif
  assign yes_values = 'yes,y,true,✓,✔' | split: ','
  assign no_values = 'no,n,false,✗,✕,×,x' | split: ','
  assign dash_values = '-,–,—,n/a' | split: ','
  assign caption = s.caption | default: s.heading | strip_html | strip
-%}

<div
  class="lib-compare lib-compare--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-columns="{{ column_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-accent: {{ s.color_accent }};
    --lib-accent-text: {{ s.color_accent_text }};
    --lib-panel: {{ s.color_panel }};
    --lib-line: {{ s.color_line }};
    --lib-hl: {{ s.color_highlight }};
    --lib-hl-text: {{ s.color_highlight_text }};
    --lib-yes: {{ s.color_yes }};
    --lib-no: {{ s.color_no }};
    --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-compare__inner">
    {%- if s.heading != blank or s.text != blank -%}
      <div class="lib-compare__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-compare__heading lib-compare__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-compare__text">{{ s.text }}</div>
        {%- endif -%}
      </div>
    {%- endif -%}

    {%- if column_count > 0 -%}
      <div class="lib-compare__scroll" role="region" tabindex="0" aria-label="{{ s.scroll_label | escape }}">
        <table class="lib-compare__table">
          {%- if caption != blank -%}
            <caption class="lib-compare__sr">{{ caption | escape }}</caption>
          {%- endif -%}
          <thead>
            <tr>
              <td class="lib-compare__corner">
                {%- if s.corner_label != blank -%}<span class="lib-compare__corner-label">{{ s.corner_label | escape }}</span>{%- endif -%}
              </td>
              {%- for column in columns -%}
                {%- liquid
                  assign c = column.settings
                  assign p = c.product
                  assign title = c.title
                  if title == blank and p != blank
                    assign title = p.title
                  endif
                  assign image = c.image
                  if image == blank and p != blank
                    assign image = p.featured_image
                  endif
                  assign url = c.button_url
                  if url == blank and p != blank
                    assign url = p.url
                  endif
                -%}
                <th scope="col" class="lib-compare__col" {% if c.highlight %}data-highlight{% endif %} {{ column.shopify_attributes }}>
                  <div class="lib-compare__col-inner">
                    {%- if c.highlight and c.highlight_label != blank -%}
                      <span class="lib-compare__badge">{{ c.highlight_label | escape }}</span>
                    {%- endif -%}
                    {%- if s.show_images and image != blank -%}
                      <div class="lib-compare__media" data-ratio="{{ s.image_ratio }}">
                        {%- assign img_loading = 'lazy' -%}
                        {%- if eager -%}{%- assign img_loading = 'eager' -%}{%- endif -%}
                        {{ image | image_url: width: 600 | image_tag: class: 'lib-compare__img', loading: img_loading, sizes: '(min-width: 990px) 240px, 45vw', widths: '240, 360, 480, 600', alt: '' }}
                      </div>
                    {%- endif -%}
                    <span class="lib-compare__title">{{ title | escape }}</span>
                    {%- if s.show_prices -%}
                      {%- if c.price_text != blank -%}
                        <span class="lib-compare__price">{{ c.price_text | escape }}</span>
                      {%- elsif p != blank -%}
                        <span class="lib-compare__price">
                          {%- if p.price_varies -%}{{ s.from_label | escape }} {% endif -%}
                          {%- if p.compare_at_price > p.price -%}
                            <span class="lib-compare__sr">{{ s.sale_label | escape }}</span>
                            {{- p.price | money -}}
                            <span class="lib-compare__sr">{{ s.regular_label | escape }}</span>
                            <s class="lib-compare__was">{{ p.compare_at_price | money }}</s>
                          {%- else -%}
                            {{- p.price | money -}}
                          {%- endif -%}
                        </span>
                      {%- endif -%}
                    {%- endif -%}
                    {%- if c.button_label != blank -%}
                      <span class="lib-compare__cta">
                        {%- assign btn_style = c.button_style -%}
                        {%- if c.highlight and btn_style == 'outline' -%}{%- assign btn_style = 'primary' -%}{%- endif -%}
                        {%- render 'lib-button', label: c.button_label, url: url, style: btn_style, new_tab: c.button_new_tab, arrow: false -%}
                      </span>
                    {%- endif -%}
                  </div>
                </th>
              {%- endfor -%}
            </tr>
          </thead>
          <tbody>
            {%- for block in section.blocks -%}
              {%- assign b = block.settings -%}
              {%- case block.type -%}
                {%- when 'group' -%}
                  {%- if b.title != blank -%}
                    <tr class="lib-compare__group" {{ block.shopify_attributes }}>
                      <th scope="colgroup" colspan="{{ column_count | plus: 1 }}">{{ b.title | escape }}</th>
                    </tr>
                  {%- endif -%}
                {%- when 'row' -%}
                  {%- assign values = b.values | split: '|' -%}
                  <tr class="lib-compare__row" {{ block.shopify_attributes }}>
                    <th scope="row" class="lib-compare__feature">
                      <span class="lib-compare__feature-label">{{ b.feature | escape }}</span>
                      {%- if b.note != blank -%}
                        <span class="lib-compare__note">{{ b.note | escape }}</span>
                      {%- endif -%}
                    </th>
                    {%- for column in columns -%}
                      {%- assign i = forloop.index0 -%}
                      {%- assign raw = values[i] | strip -%}
                      {%- assign key = raw | downcase -%}
                      <td {% if column.settings.highlight %}data-highlight{% endif %}>
                        {%- if yes_values contains key -%}
                          <span class="lib-compare__mark lib-compare__mark--yes">
                            <svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" focusable="false"><path d="m5 10.5 3.2 3L15 6.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                            <span class="lib-compare__sr">{{ s.yes_label | escape }}</span>
                          </span>
                        {%- elsif no_values contains key -%}
                          <span class="lib-compare__mark lib-compare__mark--no">
                            <svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" focusable="false"><path d="m6.5 6.5 7 7M13.5 6.5l-7 7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
                            <span class="lib-compare__sr">{{ s.no_label | escape }}</span>
                          </span>
                        {%- elsif dash_values contains key or raw == blank -%}
                          <span class="lib-compare__mark lib-compare__mark--none">
                            <span aria-hidden="true">–</span>
                            <span class="lib-compare__sr">{{ s.none_label | escape }}</span>
                          </span>
                        {%- else -%}
                          {{ raw | escape }}
                        {%- endif -%}
                      </td>
                    {%- endfor -%}
                  </tr>
              {%- endcase -%}
            {%- endfor -%}
          </tbody>
        </table>
      </div>
    {%- endif -%}
  </div>
</div>

{% stylesheet %}
  .lib-compare {
    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-compare__inner { max-width: 1200px; margin: 0 auto; }
  .lib-compare--full { padding-inline: clamp(1rem, 3vw, 2.5rem); }
  .lib-compare--full .lib-compare__inner { max-width: none; }
  @media (max-width: 749px) {
    .lib-compare { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-compare[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-compare[data-show='mobile'] { display: none; }
  }

  .lib-compare__header { max-width: 44rem; margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
  .lib-compare__header[data-align='center'] { margin-inline: auto; text-align: center; }
  .lib-compare__header[data-align='right'] { margin-inline-start: auto; text-align: right; }
  @media (max-width: 749px) {
    .lib-compare__header[data-align-m='left'] { margin-inline: 0 auto; text-align: left; }
    .lib-compare__header[data-align-m='center'] { margin-inline: auto; text-align: center; }
    .lib-compare__header[data-align-m='right'] { margin-inline: auto 0; text-align: right; }
  }
  .lib-compare__heading {
    margin: 0;
    font-size: clamp(2rem, 4vw, 3.25rem);
    line-height: 1.1;
    letter-spacing: -0.025em;
    text-wrap: balance;
  }
  .lib-compare__heading--small { font-size: clamp(1.5rem, 2.5vw, 2rem); }
  .lib-compare__heading--medium { font-size: clamp(1.75rem, 3vw, 2.5rem); }
  .lib-compare__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); }
  .lib-compare__text { margin-top: 1rem; font-size: 1.0625rem; line-height: 1.65; opacity: 0.85; }
  .lib-compare__text > :first-child { margin-top: 0; }
  .lib-compare__text > :last-child { margin-bottom: 0; }

  /* Table: scrolls inside its region; the feature column stays visible while it does. */
  /* Positioned so the screen-reader-only labels inside are clipped by the scroll area, not the page. */
  .lib-compare__scroll {
    position: relative;
    max-width: 100%;
    overflow-x: auto;
    border-radius: 16px;
    background: var(--lib-panel);
    border: 1px solid var(--lib-line);
  }
  .lib-compare__scroll:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 3px; }
  .lib-compare__table {
    width: 100%;
    min-width: calc(11rem + var(--lib-col-min) * var(--lib-cols, 3));
    --lib-col-min: 10rem;
    border-collapse: separate;
    border-spacing: 0;
    table-layout: fixed;
    font-size: 0.9375rem;
  }
  .lib-compare[data-columns='2'] .lib-compare__table { --lib-cols: 2; }
  .lib-compare[data-columns='4'] .lib-compare__table { --lib-cols: 4; }
  .lib-compare__table th,
  .lib-compare__table td {
    padding: 0.95rem 1rem;
    border-bottom: 1px solid var(--lib-line);
    text-align: center;
    vertical-align: middle;
  }
  .lib-compare__table tbody tr:last-child > * { border-bottom: 0; }
  .lib-compare__table tr > :first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    width: 11rem;
    text-align: left;
    background: var(--lib-panel);
  }
  @media (min-width: 990px) {
    .lib-compare__table tr > :first-child { width: 28%; }
  }
  /* Narrow screens: a slimmer feature column so more than one product shows before scrolling. */
  @media (max-width: 749px) {
    .lib-compare__table { --lib-col-min: 8rem; min-width: calc(8.5rem + var(--lib-col-min) * var(--lib-cols, 3)); font-size: 0.875rem; }
    .lib-compare__table th,
    .lib-compare__table td { padding: 0.8rem 0.65rem; }
    .lib-compare__table tr > :first-child { width: 8.5rem; }
    .lib-compare__media { max-width: 6.5rem; }
    .lib-compare__title { font-size: 0.9375rem; }
  }
  .lib-compare__table [data-highlight] { background: color-mix(in srgb, var(--lib-hl) 9%, var(--lib-panel)); }

  .lib-compare__table .lib-compare__corner { vertical-align: bottom; }
  .lib-compare__corner-label { font-size: 0.8125rem; font-weight: 600; opacity: 0.7; }
  .lib-compare__table .lib-compare__col { position: relative; vertical-align: top; padding-top: 3rem; font-weight: 400; }
  .lib-compare__col-inner { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
  .lib-compare__badge {
    position: absolute;
    top: 0.9rem;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: var(--lib-hl);
    color: var(--lib-hl-text);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
  }
  .lib-compare__media {
    width: 100%;
    max-width: 10rem;
    border-radius: 10px;
    overflow: hidden;
    background: color-mix(in srgb, var(--lib-text) 5%, transparent);
    aspect-ratio: 1;
  }
  .lib-compare__media[data-ratio='portrait'] { aspect-ratio: 4 / 5; }
  .lib-compare__media[data-ratio='landscape'] { aspect-ratio: 4 / 3; }
  .lib-compare__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-compare__title { font-size: 1rem; font-weight: 600; line-height: 1.3; text-wrap: balance; }
  .lib-compare__price { font-size: 0.9375rem; font-variant-numeric: tabular-nums; }
  .lib-compare__was { margin-inline-start: 0.35em; opacity: 0.6; }
  .lib-compare__cta { margin-top: 0.25rem; }
  .lib-compare__cta .lib-btn { font-size: 0.875rem; padding: 0.75em 1.2em; }

  .lib-compare__feature { font-weight: 500; }
  .lib-compare__feature-label { display: block; }
  .lib-compare__note { display: block; margin-top: 0.2rem; font-size: 0.8125rem; font-weight: 400; opacity: 0.7; }
  .lib-compare__group th {
    padding-top: 1.4rem;
    background: var(--lib-panel);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--lib-accent);
    text-align: left;
  }
  .lib-compare__mark { display: inline-grid; place-items: center; }
  .lib-compare__mark--yes {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: color-mix(in srgb, var(--lib-yes) 14%, transparent);
    color: var(--lib-yes);
  }
  .lib-compare__mark--no { color: var(--lib-no); }
  .lib-compare__mark--none { opacity: 0.55; }
  .lib-compare__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* Motion: rows fade in as the table scrolls into view (scroll-driven animations only). */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .lib-compare[data-animate] .lib-compare__table tbody tr {
        animation: lib-compare-in linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 70%;
      }
    }
  }
  @keyframes lib-compare-in {
    from { opacity: 0; transform: translateY(10px); }
  }
{% endstylesheet %}

{% schema %}
{
  "name": "Lib · Comparison table",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 40,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Which set is right for you?" },
    {
      "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": "medium"
    },
    { "type": "richtext", "id": "text", "label": "Text" },

    { "type": "header", "content": "Table" },
    { "type": "paragraph", "content": "Add 2 to 4 Column blocks, then Row blocks. Type one value per column in a row, separated by |. yes, no and - become icons." },
    { "type": "checkbox", "id": "show_images", "label": "Show column images", "default": true },
    {
      "type": "select",
      "id": "image_ratio",
      "label": "Image shape",
      "options": [
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait" },
        { "value": "landscape", "label": "Landscape" }
      ],
      "default": "square"
    },
    { "type": "checkbox", "id": "show_prices", "label": "Show prices", "default": true },
    { "type": "text", "id": "corner_label", "label": "Feature column heading", "info": "Optional. Shown above the feature names." },
    { "type": "text", "id": "caption", "label": "Table name (screen readers)", "info": "The heading is used when this is empty." },
    { "type": "text", "id": "scroll_label", "label": "Table area name (screen readers)", "default": "Comparison table, scrolls sideways on small screens" },
    { "type": "text", "id": "yes_label", "label": "Yes icon text (screen readers)", "default": "Included" },
    { "type": "text", "id": "no_label", "label": "No icon text (screen readers)", "default": "Not included" },
    { "type": "text", "id": "none_label", "label": "Dash text (screen readers)", "default": "Not applicable" },
    { "type": "text", "id": "from_label", "label": "Price prefix when variants differ", "default": "From" },
    { "type": "text", "id": "sale_label", "label": "Sale price (screen readers)", "default": "Sale price" },
    { "type": "text", "id": "regular_label", "label": "Regular price (screen readers)", "default": "Regular price" },

    { "type": "header", "content": "Layout" },
    {
      "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": "Heading alignment on desktop", "default": "center" },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Heading alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" },
        { "value": "right", "label": "Right" }
      ],
      "default": "left"
    },

    { "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", "default": "#231F1B" },
    { "type": "color", "id": "color_accent", "label": "Accent (solid button, group headings)", "default": "#7A5A3A" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" },
    { "type": "color", "id": "color_panel", "label": "Table background", "default": "#FFFFFF" },
    { "type": "color", "id": "color_line", "label": "Lines", "default": "#E7E0D6" },
    { "type": "color", "id": "color_highlight", "label": "Highlighted column", "default": "#7A5A3A" },
    { "type": "color", "id": "color_highlight_text", "label": "Highlight label text", "default": "#FFFFFF" },
    { "type": "color", "id": "color_yes", "label": "Yes icon", "default": "#3F6B45" },
    { "type": "color", "id": "color_no", "label": "No icon", "default": "#9A3B2E" },

    { "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": 88 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 88 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 48 },
    { "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 rows in on scroll", "info": "Only in browsers that support scroll-driven animations; never when reduced motion is on.", "default": false }
  ],
  "blocks": [
    {
      "type": "column",
      "name": "Column",
      "limit": 4,
      "settings": [
        { "type": "product", "id": "product", "label": "Product", "info": "Optional. Supplies the image, title, price and link." },
        { "type": "image_picker", "id": "image", "label": "Image", "info": "Replaces the product image." },
        { "type": "text", "id": "title", "label": "Title", "info": "Replaces the product title.", "default": "Product" },
        { "type": "text", "id": "price_text", "label": "Price text", "info": "Optional. Replaces the product price, e.g. for a plan." },
        { "type": "checkbox", "id": "highlight", "label": "Highlight this column", "default": false },
        { "type": "text", "id": "highlight_label", "label": "Highlight label", "info": "Your own words, e.g. Our pick.", "visible_if": "{{ block.settings.highlight }}" },
        { "type": "text", "id": "button_label", "label": "Button label", "default": "View product" },
        { "type": "url", "id": "button_url", "label": "Button link", "info": "The product page is used when this is empty." },
        {
          "type": "select",
          "id": "button_style",
          "label": "Button style",
          "options": [
            { "value": "primary", "label": "Solid" },
            { "value": "outline", "label": "Outline" },
            { "value": "text", "label": "Text link" }
          ],
          "default": "outline"
        },
        { "type": "checkbox", "id": "button_new_tab", "label": "Open link in a new tab", "default": false }
      ]
    },
    {
      "type": "group",
      "name": "Row group heading",
      "settings": [
        { "type": "text", "id": "title", "label": "Heading", "default": "What's included" }
      ]
    },
    {
      "type": "row",
      "name": "Row",
      "settings": [
        { "type": "text", "id": "feature", "label": "Feature", "default": "Feature" },
        { "type": "text", "id": "note", "label": "Note", "info": "Optional short line under the feature." },
        { "type": "text", "id": "values", "label": "Values", "info": "One per column, separated by |. yes, no and - become icons; anything else shows as typed.", "default": "yes | yes | no" }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Comparison table",
      "blocks": [
        { "type": "column", "settings": { "title": "Essentials" } },
        { "type": "column", "settings": { "title": "Everyday", "highlight": true, "highlight_label": "Our pick" } },
        { "type": "column", "settings": { "title": "Hosting" } },
        { "type": "row", "settings": { "feature": "Place settings", "values": "2 | 4 | 6" } },
        { "type": "row", "settings": { "feature": "Dinner plates", "values": "yes | yes | yes" } },
        { "type": "row", "settings": { "feature": "Serving bowl", "values": "no | yes | yes" } },
        { "type": "row", "settings": { "feature": "Dishwasher safe", "values": "yes | yes | yes" } }
      ]
    }
  ]
}
{% 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.
