Comparison table section
for Dawn and Horizon.
This side-by-side comparison table fits on any Shopify product, pricing, or landing page. You add columns as blocks in the theme editor, set row labels in the section settings, and enter yes, no, or custom text for each cell. You can mark one column to stand out with a full border and inverted header. It works entirely without third-party apps.
Preview the comparison table before editing code.
Adjust the decision copy, highlight badge, accent, and featured column. In Shopify, columns are blocks and row labels live in the section settings.
Compare your options
Help shoppers choose without opening another tab.
{% comment %}
Jelonyx · Comparison Table Section
Compatible with Dawn 12+, Horizon, and most OS 2.0 themes
Source: jelonyx.com/shopify/sections/comparison-table
{% endcomment %}
{%- if section.blocks.size > 0 -%}
<section
id="jlx-ct-{{ section.id }}"
class="jlx-ct"
>
<div class="jlx-ct__inner">
{%- if section.settings.heading != blank -%}
<h2 class="jlx-ct__heading">{{ section.settings.heading }}</h2>
{%- endif -%}
{%- if section.settings.subheading != blank -%}
<p class="jlx-ct__subheading">{{ section.settings.subheading }}</p>
{%- endif -%}
<div class="jlx-ct__wrap">
<div
class="jlx-ct__table"
style="--jlx-ct-data-cols: {{ section.blocks.size }}"
>
<div class="jlx-ct__row jlx-ct__row--head">
<div class="jlx-ct__cell jlx-ct__cell--label-head"></div>
{%- for block in section.blocks -%}
<div
class="jlx-ct__cell jlx-ct__cell--col-head{%- if block.settings.highlight %} jlx-ct__cell--hl jlx-ct__cell--hl-head{%- endif -%}"
{{ block.shopify_attributes }}
>
{%- if block.settings.highlight and section.settings.badge_label != blank -%}
<span class="jlx-ct__badge">{{ section.settings.badge_label }}</span>
{%- endif -%}
<p class="jlx-ct__col-name">{{ block.settings.col_title | default: 'Option' }}</p>
{%- if block.settings.price != blank -%}
<p class="jlx-ct__col-price">{{ block.settings.price }}</p>
{%- endif -%}
{%- if block.settings.price_note != blank -%}
<p class="jlx-ct__col-note">{{ block.settings.price_note }}</p>
{%- endif -%}
{%- if block.settings.cta_label != blank -%}
<a
href="{{ block.settings.cta_url | default: '#' }}"
class="jlx-ct__cta{%- if block.settings.highlight %} jlx-ct__cta--primary{%- endif -%}"
>{{ block.settings.cta_label }}</a>
{%- endif -%}
</div>
{%- endfor -%}
</div>
{%- for i in (1..8) -%}
{%- assign ri = "0" | append: i -%}
{%- assign label_key = "row_" | append: ri | append: "_label" -%}
{%- assign row_label = section.settings[label_key] -%}
{%- if row_label != blank -%}
<div class="jlx-ct__row">
<div class="jlx-ct__cell jlx-ct__cell--label">{{ row_label }}</div>
{%- for block in section.blocks -%}
{%- assign val_key = "row_" | append: ri | append: "_value" -%}
{%- assign cell_val = block.settings[val_key] -%}
<div class="jlx-ct__cell{%- if block.settings.highlight %} jlx-ct__cell--hl{%- endif -%}">
{%- case cell_val -%}
{%- when 'yes' -%}
<span class="jlx-ct__yes" aria-label="Included">✓</span>
{%- when 'no' -%}
<span class="jlx-ct__no" aria-label="Not included">-</span>
{%- when '' -%}
<span class="jlx-ct__no" aria-label="Not available">-</span>
{%- else -%}
<span>{{ cell_val }}</span>
{%- endcase -%}
</div>
{%- endfor -%}
</div>
{%- endif -%}
{%- endfor -%}
</div>
</div>
</div>
</section>
<style>
#jlx-ct-{{ section.id }} {
padding: clamp(40px, 8vw, 80px) 20px;
background: var(--color-background, #fff);
}
.jlx-ct__inner {
max-width: 1100px;
margin: 0 auto;
}
.jlx-ct__heading {
font-size: clamp(22px, 3.5vw, 34px);
font-weight: 700;
line-height: 1.2;
color: var(--color-foreground, #1a1a1a);
margin: 0 0 10px;
text-align: center;
}
.jlx-ct__subheading {
font-size: 15px;
line-height: 1.6;
color: var(--color-foreground-secondary, #6b6b6b);
text-align: center;
margin: 0 0 40px;
}
.jlx-ct__wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.jlx-ct__table {
display: grid;
grid-template-columns: minmax(140px, 200px) repeat(var(--jlx-ct-data-cols, 3), 1fr);
border: 1px solid var(--color-border, rgba(0,0,0,0.12));
border-radius: 10px;
overflow: hidden;
width: 100%;
min-width: 400px;
}
.jlx-ct__row { display: contents; }
.jlx-ct__cell {
padding: 13px 18px;
border-bottom: 1px solid var(--color-border, rgba(0,0,0,0.08));
display: flex;
align-items: center;
font-size: 14px;
color: var(--color-foreground, #1a1a1a);
background: var(--color-background, #fff);
}
.jlx-ct__cell--label-head {
background: var(--color-background-2, rgba(0,0,0,0.03));
}
.jlx-ct__cell--col-head {
flex-direction: column;
align-items: flex-start;
gap: 5px;
padding: 22px 18px 18px;
background: var(--color-background-2, rgba(0,0,0,0.03));
}
.jlx-ct__cell--label {
font-size: 13px;
font-weight: 500;
color: var(--color-foreground-secondary, #6b6b6b);
}
.jlx-ct__cell--hl {
border-left: 2px solid var(--color-button, #1a1a1a);
border-right: 2px solid var(--color-button, #1a1a1a);
}
.jlx-ct__cell--hl-head {
background: var(--color-button, #1a1a1a);
color: var(--color-button-text, #fff);
border-top: 2px solid var(--color-button, #1a1a1a);
}
.jlx-ct__row:last-child .jlx-ct__cell { border-bottom: none; }
.jlx-ct__row:last-child .jlx-ct__cell--hl {
border-bottom: 2px solid var(--color-button, #1a1a1a);
}
.jlx-ct__badge {
display: inline-block;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
background: rgba(255,255,255,0.18);
color: var(--color-button-text, #fff);
padding: 3px 8px;
border-radius: 2px;
}
.jlx-ct__col-name {
font-size: 15px;
font-weight: 700;
margin: 0;
line-height: 1.3;
}
.jlx-ct__col-price {
font-size: 22px;
font-weight: 700;
margin: 0;
line-height: 1.2;
}
.jlx-ct__col-note {
font-size: 12px;
opacity: 0.65;
margin: 0;
}
.jlx-ct__cta {
display: inline-block;
margin-top: 4px;
padding: 8px 14px;
border: 1px solid currentColor;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
color: inherit;
transition: opacity 0.15s;
}
.jlx-ct__cta:hover { opacity: 0.75; }
.jlx-ct__cta--primary {
background: var(--color-button-text, #fff);
color: var(--color-button, #1a1a1a);
border-color: transparent;
}
.jlx-ct__yes {
color: #1a9463;
font-size: 16px;
font-weight: 700;
line-height: 1;
}
.jlx-ct__no {
color: var(--color-foreground-secondary, #aaa);
font-size: 16px;
line-height: 1;
}
</style>
{%- else -%}
{%- if request.design_mode -%}
<div style="padding:60px 20px;text-align:center;border:2px dashed rgba(0,0,0,0.12);border-radius:8px;">
<p style="color:#888;font-size:14px;margin:0;">Add columns using the section settings to build your comparison table.</p>
</div>
{%- endif -%}
{%- endif -%}
{% schema %}
{
"name": "Comparison table",
"tag": "section",
"class": "section",
"max_blocks": 4,
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"placeholder": "Compare options"
},
{
"type": "text",
"id": "subheading",
"label": "Subheading"
},
{
"type": "text",
"id": "badge_label",
"label": "Highlight badge text",
"default": "Most popular",
"info": "Appears on the highlighted column header."
},
{ "type": "header", "content": "Row labels" },
{ "type": "text", "id": "row_01_label", "label": "Row 1 label", "placeholder": "Shipping" },
{ "type": "text", "id": "row_02_label", "label": "Row 2 label", "placeholder": "Returns" },
{ "type": "text", "id": "row_03_label", "label": "Row 3 label" },
{ "type": "text", "id": "row_04_label", "label": "Row 4 label" },
{ "type": "text", "id": "row_05_label", "label": "Row 5 label" },
{ "type": "text", "id": "row_06_label", "label": "Row 6 label" },
{ "type": "text", "id": "row_07_label", "label": "Row 7 label" },
{ "type": "text", "id": "row_08_label", "label": "Row 8 label" }
],
"blocks": [
{
"type": "column",
"name": "Column",
"settings": [
{ "type": "text", "id": "col_title", "label": "Column title", "placeholder": "Starter" },
{ "type": "text", "id": "price", "label": "Price", "placeholder": "$0" },
{ "type": "text", "id": "price_note", "label": "Price note", "placeholder": "per month" },
{
"type": "checkbox",
"id": "highlight",
"label": "Highlight this column",
"default": false
},
{ "type": "text", "id": "cta_label", "label": "Button label" },
{ "type": "url", "id": "cta_url", "label": "Button URL" },
{
"type": "header",
"content": "Row values",
"info": "Enter 'yes' for ✓, 'no' for -, or any custom text."
},
{ "type": "text", "id": "row_01_value", "label": "Row 1", "placeholder": "yes" },
{ "type": "text", "id": "row_02_value", "label": "Row 2", "placeholder": "no" },
{ "type": "text", "id": "row_03_value", "label": "Row 3" },
{ "type": "text", "id": "row_04_value", "label": "Row 4" },
{ "type": "text", "id": "row_05_value", "label": "Row 5" },
{ "type": "text", "id": "row_06_value", "label": "Row 6" },
{ "type": "text", "id": "row_07_value", "label": "Row 7" },
{ "type": "text", "id": "row_08_value", "label": "Row 8" }
]
}
],
"presets": [
{
"name": "Comparison table",
"category": "Comparison"
}
]
}
{% endschema %}How to add the section
- 01Open your theme code editor.
In Shopify Admin, go to Online Store → Themes. On your active theme, click the three-dot menu and select Edit code.
- 02Create a new section file.
Under the Sections folder, click Add a new section. Name it
jlx-comparison-tableand click Done. - 03Paste the full section code.
Delete any placeholder content in the new file, paste the entire code block above, and save.
- 04Add the section to a template.
Open the theme editor (Customize). Navigate to the page template where you want the table. Click Add section and select Comparison table.
- 05Add columns as blocks.
Inside the section panel, click Add block for each column (up to 4). Set the column title, price, price note, and CTA per block. Check Highlight this column on the one you want to stand out.
- 06Set row labels and values.
In the section settings (not the block panel), enter the row labels, which become the feature names in the left column. Then go into each column block and enter
yes,no, or custom text for each matching row. Save.
Schema settings
Section-level settings (apply to the whole table):
headingtextOptional heading above the table.subheadingtextOptional subheading below the heading.badge_labeltextText for the badge on the highlighted column. Defaults to "Most popular".row_01_label … row_08_labeltextRow feature labels in the left column. Rows with blank labels are skipped, so no empty rows appear.Block settings (per column, up to 4 columns):
col_titletextColumn heading. Shown prominently in the header cell.pricetextFree-text price. e.g. "$29", "Free", "Custom".price_notetextSmall line below the price. e.g. "per month", "one-time".highlightcheckboxInverts the column header and adds a full border around the column.cta_labeltextButton copy. Leave blank to omit the button.cta_urlurlURL for the column button.row_01_value … row_08_valuetextCell value matching each row label. Enter 'yes' for ✓, 'no' for -, or any text.How it works
The table is a CSS grid. Rows use display: contents so their cells become direct grid children and line up correctly across columns without a real <table> element. The column count is passed as a CSS custom property --jlx-ct-data-cols set inline via Liquid, so repeat(var(--jlx-ct-data-cols, 3), 1fr) in the grid template always matches the actual block count.
Row labels and cell values are linked by index. Both the section settings and each column block use the same key pattern. For example, row_01_label in section settings pairs with row_01_value in each block. Rows with a blank label are skipped entirely using {% if row_label != blank %}, so unused row slots produce no markup.
Cell values are matched with a {% case %} statement. yes renders a green ✓, no or blank renders a muted -, and anything else renders as plain text. This lets you mix check marks with custom text like Unlimited or 5 users in the same column.
The highlight column is styled entirely with CSS. The header cell gets an inverted background (--color-button) and a top border. Body cells in the same column get left and right borders. The last row's highlighted cell gets a bottom border to close the box. No JavaScript, no class toggling at runtime.
In the theme editor with no blocks added, the section shows a dashed placeholder via request.design_mode. On the live storefront with no blocks, it renders nothing, leaving no empty space.
Compatibility
Tested against Dawn 12+ and Horizon. The section reads --color-background, --color-foreground, --color-button, --color-button-text, and --color-border from the theme, so it picks up your brand colours automatically.
On themes that do not define --color-button, the highlight column falls back to #1a1a1a (near-black). Override .jlx-ct__cell--hl and .jlx-ct__cell--hl-head in the section CSS to hard-code your theme's accent colour.
display: contents is supported in all modern browsers and in Safari 11.1+. Shopify's storefront renderer requires a modern browser, so this is not a concern for OS 2.0 themes.
Limitations
- Four columns maximum:
max_blocks: 4is set in the schema. On narrow viewports three or four columns can feel crowded. Horizontal scroll is enabled viaoverflow-x: autoon the wrapper, but consider limiting to two or three columns for mobile stores. - Eight rows maximum: the Liquid loop runs
{% for i in (1..8) %}. Extend to a higher number by changing the range in the section code and adding corresponding schema settings. - Text-only cells: cells render plain text or the ✓/- symbols. Per-cell formatting (bold, links, icons) requires editing the
{% case %}block in the section code. - One highlighted column per section: there is no schema enforcement preventing multiple blocks from having
highlight: true, but visually the effect works best on a single column. If two columns are highlighted, both get the bordered treatment. - Headless storefronts: this is a Liquid section and does not apply to Hydrogen or other headless setups.
Need this built for your store?
If you want the comparison table styled to match your brand, extended with images per column, or wired up to product data, we can scope and deliver that.