jelonyx
No-app fixes · Fix 04

Shopify size guide popup
without an app.

Size uncertainty is one of the top reasons customers abandon apparel purchases. Most stores pay a monthly app fee for a modal that shows a table. This fix delivers the same result with a single Liquid snippet without an app. It includes a trigger link, animated modal, and accessible close behaviour.

DifficultyBeginner
Time~15 minutes
CostFree
Theme accessRequired
Compatible with:Dawn 12+HorizonSenseCraftRefreshMost OS 2.0 themes
Preview workbench

Preview the modal and table.

Edit the modal title, measurement note, and unit label. The free snippet keeps table content in code; a paid version can move it into metafields or theme settings.

Live storefront preview
Variant options
XSSML

Size guide

All measurements in centimetres. (cm)

SizeChestWaistHips
XS81-8661-6686-91
S86-9166-7191-96
M91-9671-7696-101
L96-10276-81101-107

The code

One file. Edit the table rows to match your product's measurements.

snippets/jlx-sg.liquid
Liquid · CSS · JS
{% comment %}
  Jelonyx · Size Guide Popup : no app required
  Compatible with Dawn 12+, Horizon, and most Online Store 2.0 themes
  Source: jelonyx.com/shopify/no-app/size-guide-popup
{% endcomment %}

{%- comment -%}
  Edit the sizing table below to match your product's measurements.
  Add or remove rows as needed. The table scrolls horizontally on small screens.
{%- endcomment -%}

<button
  type="button"
  class="jlx-sg-trigger"
  aria-haspopup="dialog"
  aria-controls="jlx-sg-modal"
>
  Size guide
</button>

<div
  id="jlx-sg-modal"
  class="jlx-sg-modal"
  role="dialog"
  aria-modal="true"
  aria-labelledby="jlx-sg-title"
  aria-hidden="true"
>
  <div class="jlx-sg-overlay" data-jlx-sg-close></div>
  <div class="jlx-sg-panel">

    <div class="jlx-sg-panel__head">
      <h2 id="jlx-sg-title" class="jlx-sg-panel__title">Size guide</h2>
      <button
        type="button"
        class="jlx-sg-panel__close"
        aria-label="Close size guide"
        data-jlx-sg-close
      >
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
          <path d="M5 5l10 10M15 5L5 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
        </svg>
      </button>
    </div>

    <div class="jlx-sg-panel__body">
      <p class="jlx-sg-note">All measurements in centimetres (cm).</p>
      <div class="jlx-sg-table-wrap">
        <table class="jlx-sg-table">
          <thead>
            <tr>
              <th scope="col">Size</th>
              <th scope="col">Chest</th>
              <th scope="col">Waist</th>
              <th scope="col">Hips</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>XS</td><td>81–86</td><td>61–66</td><td>86–91</td></tr>
            <tr><td>S</td><td>86–91</td><td>66–71</td><td>91–96</td></tr>
            <tr><td>M</td><td>91–96</td><td>71–76</td><td>96–101</td></tr>
            <tr><td>L</td><td>96–102</td><td>76–81</td><td>101–107</td></tr>
            <tr><td>XL</td><td>102–107</td><td>81–86</td><td>107–112</td></tr>
            <tr><td>XXL</td><td>107–112</td><td>86–91</td><td>112–117</td></tr>
          </tbody>
        </table>
      </div>
      <p class="jlx-sg-note" style="margin-top: 14px;">Measure around the fullest part of each area with a soft tape measure.</p>
    </div>

  </div>
</div>

<style>
  .jlx-sg-trigger {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font-family: inherit;
    font-size: 13px;
    color: var(--color-foreground, #1a1a1a);
    text-decoration: underline;
    text-underline-offset: 3px;
    opacity: 0.6;
    transition: opacity 0.15s ease;
  }
  .jlx-sg-trigger:hover {
    opacity: 1;
  }
  .jlx-sg-modal {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  @media (min-width: 640px) {
    .jlx-sg-modal { align-items: center; }
  }
  .jlx-sg-modal.jlx-is-open {
    pointer-events: auto;
    opacity: 1;
  }
  .jlx-sg-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    cursor: pointer;
  }
  .jlx-sg-panel {
    position: relative;
    z-index: 1;
    background: var(--color-background, #ffffff);
    width: 100%;
    max-width: 560px;
    max-height: 85vh;
    overflow-y: auto;
    border-radius: 8px 8px 0 0;
    transform: translateY(24px);
    transition: transform 0.25s ease;
  }
  @media (min-width: 640px) {
    .jlx-sg-panel {
      border-radius: 8px;
      max-height: 80vh;
    }
  }
  .jlx-sg-modal.jlx-is-open .jlx-sg-panel {
    transform: translateY(0);
  }
  .jlx-sg-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px 24px;
    border-bottom: 1px solid var(--color-border, rgba(0, 0, 0, 0.1));
    position: sticky;
    top: 0;
    background: var(--color-background, #ffffff);
    z-index: 1;
  }
  .jlx-sg-panel__title {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--color-foreground, #1a1a1a);
  }
  .jlx-sg-panel__close {
    background: none;
    border: none;
    padding: 4px;
    cursor: pointer;
    color: var(--color-foreground, #1a1a1a);
    opacity: 0.45;
    transition: opacity 0.15s ease;
    display: flex;
    align-items: center;
    line-height: 1;
  }
  .jlx-sg-panel__close:hover { opacity: 1; }
  .jlx-sg-panel__body {
    padding: 24px;
  }
  .jlx-sg-note {
    margin: 0 0 14px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-foreground, #1a1a1a);
    opacity: 0.55;
  }
  .jlx-sg-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--color-border, rgba(0, 0, 0, 0.1));
    border-radius: 4px;
  }
  .jlx-sg-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    color: var(--color-foreground, #1a1a1a);
  }
  .jlx-sg-table th,
  .jlx-sg-table td {
    padding: 10px 16px;
    text-align: left;
    border-bottom: 1px solid var(--color-border, rgba(0, 0, 0, 0.08));
    white-space: nowrap;
  }
  .jlx-sg-table th {
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.5;
    background: var(--color-background, #ffffff);
  }
  .jlx-sg-table tbody tr:last-child td { border-bottom: none; }
  .jlx-sg-table tbody tr:hover td {
    background: var(--color-border, rgba(0, 0, 0, 0.03));
  }
</style>

<script>
  (function () {
    "use strict";

    var modal = document.getElementById("jlx-sg-modal");
    if (!modal) return;

    var trigger = document.querySelector(".jlx-sg-trigger");
    var prevFocus = null;

    function open() {
      prevFocus = document.activeElement;
      modal.setAttribute("aria-hidden", "false");
      modal.classList.add("jlx-is-open");
      modal.querySelector(".jlx-sg-panel__close").focus();
      document.body.style.overflow = "hidden";
    }

    function close() {
      modal.classList.remove("jlx-is-open");
      modal.setAttribute("aria-hidden", "true");
      document.body.style.overflow = "";
      if (prevFocus && prevFocus.focus) prevFocus.focus();
    }

    if (trigger) trigger.addEventListener("click", open);

    modal.addEventListener("click", function (e) {
      if (e.target.closest("[data-jlx-sg-close]")) close();
    });

    document.addEventListener("keydown", function (e) {
      if (e.key === "Escape" && modal.classList.contains("jlx-is-open")) close();
    });
  })();
</script>

How to install

  1. 01
    Open 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.

  2. 02
    Create a new snippet.

    Under the Snippets folder, click Add a new snippet. Name it jlx-sg and click Done.

  3. 03
    Paste the code and edit your sizing table.

    Delete any placeholder content and paste the entire code block above. Then update the measurements in the <tbody> rows to match your product. Add or remove <tr> rows as needed, and update the column headers if your sizing dimensions differ. Save the file.

  4. 04
    Add the render tag via the theme customizer.

    In Shopify Admin, go to Online Store → Themes → Customize. Navigate to a product page. Click on the product section in the left panel, then click Add block → Custom Liquid. Enter the following, then drag the block to just below the variant options picker. Save.

    {% render 'jlx-sg' %}

    Alternatively, open sections/main-product.liquid in the code editor. Find the line that starts with {% schema %} and add the render tag on the line directly above it.

  5. 05
    Test on a product page.

    Open a product page. A Size guide link should appear near the variant options. Click it to fade in the modal and slide up from the bottom on mobile, or appear centered on desktop. Test all three close methods: the ✕ button, clicking the dark overlay, and pressing Escape.

How it works

The snippet renders two things: a <button> trigger link and a hidden modal. The modal uses opacity: 0 and pointer-events: none to be invisible but present in the DOM, avoiding layout shifts on open.

On open, the JavaScript adds jlx-is-open to the modal, which triggers the CSS opacity and translate transitions. This includes a fade on the overlay and a slide-up on the panel. On mobile it sheets up from the bottom. On screens wider than 640px it centres vertically. Both are handled purely with CSS media queries.

The script stores the element that had focus before opening and returns focus to it on close. It also sets aria-hidden on the modal container and the role="dialog" / aria-modal attributes ensure screen readers understand the modal context.

Three close paths are wired up: data-jlx-sg-close on both the X button and the overlay (handled by a single delegated click listener), plus an Escape key listener that only fires when the modal is open.

Compatibility

Tested against Dawn 12+ and Horizon. The modal panel reads --color-background, --color-foreground, and --color-border from your theme, so the colours match without any CSS edits.

The sizing table wraps in a horizontally scrollable container, so it renders correctly on narrow screens without overflowing. The sticky header in the modal panel ensures the title and close button are always visible even when scrolling through a long table.

If your theme already has its own modal system that also uses document.body.style.overflow = "hidden", opening this modal while another is open may not restore scroll correctly on close. In that case, remove the overflow lines from the snippet and handle scroll lock at the theme level.

Limitations

  • One size chart per snippet: the snippet renders one fixed table. If your store has products with different sizing systems (e.g. tops vs bottoms), you would need separate snippet files or a more involved solution that reads the current product to decide which table to show.
  • Table content is in code: the measurements live directly in the snippet file. Editing them requires access to the theme code editor. For a version where non-technical team members can manage the table from the Shopify customizer, a metafield-driven section is the cleaner approach.
  • No focus trap: the snippet returns focus to the trigger on close and focuses the close button on open, but does not implement a full focus trap. Keyboard users can tab out of the modal into the page behind it while it is open. For a stricter accessible implementation, a focus trap script is needed.
  • Headless storefronts: this is a Liquid snippet and does not apply to Hydrogen or other headless setups.
No-App Shopify Fix Sprint

Need this installed?

If you would rather not edit theme code, we can install and style the size guide for your store. We will populate your actual measurements and set it up to show the right table for each product type. No code access is required to update it later.