DEV Community

Avni Arora
Avni Arora

Posted on

Building a Dependency-Free Table Size Calculator for a Shopify Collection Page

Most product filters answer "what do you sell?" Very few answer "will this fit?"

I work on the Shopify store for Ramaya, a home decor brand. One of the collections is table runner and placemat sets: one runner plus six matching placemats. Customer questions on that page were almost never about colour or fabric. They were about size. "Will a 72 inch runner fit my table?" "Will the runner cover the placemats?"

So I built a small calculator that sits on the collection page and answers those questions. It has no libraries and no app install. It's one Liquid section and about 40 lines of JavaScript.

The rules behind the math

Before writing code, I needed rules a customer would accept. These are the standard table linen conventions:

  • Runner length = table length + 12 to 24 inches. That gives 6 to 12 inches of drop at each end.
  • Runner width ≈ one-third of the table width, kept between 12 and 16 inches. Narrower looks lost, wider starts to look like a tablecloth.
  • Placemats are 12 × 18 inches, with the 12-inch side at the table edge.
  • Each seat needs about 24 inches of edge.

The interesting part is the collision check. Two facing placemats take 24 inches of the table's width. Whatever is left is the centre gap. If the runner is wider than that gap, it overlaps the placemats.

The core function

const PLACEMAT_DEPTH = 12; // inches, the side facing the table edge
const SEAT_SPACE = 24;     // inches of edge per diner

function recommendTableSet({ lengthIn, widthIn }) {
  const runnerMin = lengthIn + 12;
  const runnerMax = lengthIn + 24;

  // one-third of the width, clamped to 12–16 inches
  const runnerWidth = Math.min(16, Math.max(12, Math.round(widthIn / 3)));

  // space left between two facing placemats
  const centreGap = widthIn - PLACEMAT_DEPTH * 2;
  const overlapPerSide = Math.max(0, (runnerWidth - centreGap) / 2);

  // seats along both long sides, plus ends only on wider tables
  const seatsPerSide = Math.floor(lengthIn / SEAT_SPACE);
  const endSeats = widthIn >= 36 ? 2 : 0;
  const seats = seatsPerSide * 2 + endSeats;

  return { runnerMin, runnerMax, runnerWidth, centreGap, overlapPerSide, seats };
}
Enter fullscreen mode Exit fullscreen mode

Run it against three common table sizes:

Table (in) Runner length Runner width Centre gap Overlap per side Seats
30 × 48 60–72 12 6 3 4
36 × 60 72–84 12 12 0 6
40 × 72 84–96 13 16 0 8

The 30-inch table is the case customers kept asking about. A 12-inch runner overlaps each placemat by 3 inches. That isn't a defect. It's a layered look, but people want to know before they buy.

The end-seat rule is a heuristic. My first version gave a 30 × 48 table six seats, which is wrong for most 4 seaters. Adding end seats only when the table is at least 36 inches wide fixed it for the sizes our customers actually own.

Wiring it to the page

The section markup, saved as sections/table-size-calculator.liquid:

<div class="table-calc">
  <form id="table-calc-form">
    <label>Table length (inches)
      <input type="number" name="length" min="24" max="144" required>
    </label>
    <label>Table width (inches)
      <input type="number" name="width" min="20" max="60" required>
    </label>
    <button type="submit">Check fit</button>
  </form>
  <p id="table-calc-result" aria-live="polite"></p>
</div>

<script src="{{ 'table-calc.js' | asset_url }}" defer></script>

{% schema %}
{
  "name": "Table size calculator",
  "presets": [{ "name": "Table size calculator" }]
}
{% endschema %}
Enter fullscreen mode Exit fullscreen mode

The presets block lets the merchant add the section to the collection template from the theme editor. No code change is needed per page.

Then the handler in assets/table-calc.js, below the function above:

document.getElementById('table-calc-form').addEventListener('submit', (e) => {
  e.preventDefault();
  const data = new FormData(e.target);
  const r = recommendTableSet({
    lengthIn: Number(data.get('length')),
    widthIn: Number(data.get('width')),
  });

  const overlap = r.overlapPerSide > 0
    ? `The runner will overlap each placemat by about ${r.overlapPerSide}".`
    : 'The runner and placemats will sit flat with no overlap.';

  document.getElementById('table-calc-result').textContent =
    `Runner: ${r.runnerMin}–${r.runnerMax}" long, about ${r.runnerWidth}" wide. ` +
    `Seats: ${r.seats}. ${overlap}`;
});
Enter fullscreen mode Exit fullscreen mode

Two choices worth pointing out:

  • textContent, not innerHTML. The output is built from user input. Even though it's numeric, there's no reason to open an injection path.
  • aria-live="polite" on the result. Screen reader users hear the answer without the focus jumping.

What I left out, on purpose

  • Round tables. A runner on a round table follows completely different rules (usually the diameter plus drop, or two crossed runners). Mixing that into one form made it confusing, so round tables get their own calculator.
  • Centimetres. Many international customers measure in cm. The next version adds a unit toggle and divides by 2.54 before calling the same function, so the core logic doesn't change.
  • Product matching. It's tempting to filter the product grid by the result. But most sets ship with six placemats regardless of runner length, so filtering would hide valid products. The calculator informs. It doesn't filter.

Takeaways

  1. Before building any filter, read the customer questions. Ours were all "will it fit?", and none of our existing filters answered that.
  2. Find the non-obvious rule. Runner length is easy. The runner-to-placemat overlap is what customers couldn't work out on their own.
  3. Keep it out of the app ecosystem when you can. A section plus a small script loads instantly, and the merchant controls where it appears.

If you've built fit calculators for furniture, rugs or curtains, I'd like to know how you handled the edge cases, especially unit conversion and non-rectangular shapes.

Top comments (0)