Finance & Investing

Free ROI Calculator Widget

Let visitors calculate total and annualized ROI on any investment.

An interactive calculator that turns an amount invested, an amount returned (or current value), and an optional holding period into total ROI and annualized ROI. Built for finance blogs, advisors, SaaS pricing pages, and any site that talks about returns. One script tag embeds it — no plugins, no accounts.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/roi-calculator.js" data-hl-invested="50000" data-hl-returned="72000" data-hl-years="3" async></script>
<!-- Free ROI Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/roi-calculator -->
Inline (self-contained)html
<!-- Free ROI Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/roi-calculator -->
<script data-hl-invested="50000" data-hl-returned="72000" data-hl-years="3">
/*!
 * haynes lab — ROI Calculator widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/roi-calculator
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-roi-calculator';
  var STYLE_ID = 'hlw-roi-calculator-styles';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/roi-calculator.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 560px; margin: 0 auto; padding: 24px 24px 34px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__title { margin: 0 0 16px; font-size: 18px; font-weight: 600; color: #111827; }' +
      '.' + PREFIX + '__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }' +
      '.' + PREFIX + '__field { min-width: 0; }' +
      '.' + PREFIX + '__field--full { grid-column: 1 / -1; }' +
      '.' + PREFIX + '__label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; font-weight: 500; color: #374151; }' +
      '.' + PREFIX + '__input { width: 100%; padding: 9px 12px; font: inherit; font-size: 15px; color: #111827;' +
      ' background: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; }' +
      '.' + PREFIX + '__input:focus { outline: 2px solid var(--hlw-accent); outline-offset: 1px; border-color: var(--hlw-accent); }' +
      '.' + PREFIX + '__hint { font-size: 12px; color: #9ca3af; }' +
      '.' + PREFIX + '__results { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 18px; }' +
      '.' + PREFIX + '__stat { padding: 12px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; text-align: center; }' +
      '.' + PREFIX + '__stat-value { display: block; font-size: 20px; font-weight: 700; color: #111827; }' +
      '.' + PREFIX + '__stat-value--pos { color: #15803d; }' +
      '.' + PREFIX + '__stat-value--neg { color: #b91c1c; }' +
      '.' + PREFIX + '__stat-label { display: block; font-size: 12px; color: #6b7280; margin-top: 2px; }' +
      '.' + PREFIX + '__note { margin: 12px 0 0; font-size: 13px; color: #6b7280; text-align: center; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
      ' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
      '@media (max-width: 480px) {' +
      ' .' + PREFIX + ' { padding: 20px 16px 34px; }' +
      ' .' + PREFIX + '__grid, .' + PREFIX + '__results { grid-template-columns: 1fr; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function num(input) {
    var n = parseFloat(String(input.value).replace(/,/g, ''));
    return isNaN(n) ? null : n;
  }

  function build(script) {
    var accent = script.getAttribute('data-hl-accent') || '#f97316';
    var currency = (script.getAttribute('data-hl-currency') || 'USD').toUpperCase();
    var title = script.getAttribute('data-hl-title') || 'ROI Calculator';

    var fmtMoney;
    try {
      fmtMoney = new Intl.NumberFormat(undefined, { style: 'currency', currency: currency, maximumFractionDigits: 0 });
    } catch (e) {
      console.warn('[haynes lab roi-calculator] Unknown currency "' + currency + '", falling back to USD.', e);
      fmtMoney = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
    }
    function fmtPct(n) { return (n > 0 ? '+' : '') + n.toFixed(1) + '%'; }

    injectStyles();

    var root = el('div', PREFIX);
    root.style.setProperty('--hlw-accent', accent);
    root.appendChild(el('h3', PREFIX + '__title', title));

    var grid = el('div', PREFIX + '__grid');
    root.appendChild(grid);

    function field(labelText, value, hintText, full) {
      var wrap = el('div', PREFIX + '__field' + (full ? ' ' + PREFIX + '__field--full' : ''));
      var label = el('label', PREFIX + '__label', labelText);
      var input = el('input', PREFIX + '__input');
      input.type = 'number';
      input.min = '0';
      input.step = 'any';
      input.inputMode = 'decimal';
      if (value != null && value !== '') input.value = value;
      label.appendChild(input);
      wrap.appendChild(label);
      if (hintText) wrap.appendChild(el('span', PREFIX + '__hint', hintText));
      grid.appendChild(wrap);
      return input;
    }

    var investedInput = field('Amount invested', script.getAttribute('data-hl-invested') || '10000');
    var returnedInput = field('Amount returned / current value', script.getAttribute('data-hl-returned') || '');
    var yearsInput = field('Years held (optional)', script.getAttribute('data-hl-years') || '', 'Needed for annualized ROI.', true);

    var results = el('div', PREFIX + '__results');
    results.setAttribute('aria-live', 'polite');
    root.appendChild(results);

    function stat(labelText) {
      var box = el('div', PREFIX + '__stat');
      var value = el('span', PREFIX + '__stat-value', '—');
      box.appendChild(value);
      box.appendChild(el('span', PREFIX + '__stat-label', labelText));
      results.appendChild(box);
      return value;
    }

    var gainEl = stat('Net gain');
    var roiEl = stat('Total ROI');
    var annualEl = stat('Annualized ROI');

    var note = el('p', PREFIX + '__note');
    root.appendChild(note);

    function setValue(elm, text, positive) {
      elm.textContent = text;
      elm.classList.remove(PREFIX + '__stat-value--pos', PREFIX + '__stat-value--neg');
      if (positive === true) elm.classList.add(PREFIX + '__stat-value--pos');
      if (positive === false) elm.classList.add(PREFIX + '__stat-value--neg');
    }

    function update() {
      var invested = num(investedInput);
      var returned = num(returnedInput);
      var years = num(yearsInput);
      if (invested == null || invested <= 0 || returned == null || returned < 0) {
        setValue(gainEl, '—'); setValue(roiEl, '—'); setValue(annualEl, '—');
        note.textContent = 'Enter an amount invested and an amount returned to see your ROI.';
        return;
      }
      var gain = returned - invested;
      var roi = (gain / invested) * 100;
      setValue(gainEl, (gain >= 0 ? '+' : '−') + fmtMoney.format(Math.abs(gain)), gain >= 0);
      setValue(roiEl, fmtPct(roi), roi >= 0);
      if (years != null && years > 0 && returned > 0) {
        var annual = (Math.pow(returned / invested, 1 / years) - 1) * 100;
        setValue(annualEl, fmtPct(annual), annual >= 0);
        note.textContent = 'Annualized ROI is the compound yearly growth rate over ' + years + (years === 1 ? ' year.' : ' years.');
      } else {
        setValue(annualEl, '—');
        note.textContent = 'Add the number of years held to see annualized ROI.';
      }
    }

    [investedInput, returnedInput, yearsInput].forEach(function (input) {
      input.addEventListener('input', update);
    });
    update();

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/roi-calculator';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab roi-calculator] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-title"ROI Calculator"Heading displayed at the top of the calculator card.
data-hl-invested"10000"Pre-filled value for the amount invested field.
data-hl-returned(none)Pre-filled value for the amount returned / current value field. Omit to leave it blank.
data-hl-years(none)Pre-filled value for the optional years-held field, used to compute annualized ROI. Omit to leave it blank.
data-hl-currency"USD"ISO 4217 currency code (e.g. "USD", "EUR", "GBP") used to format the net gain.
data-hl-accent"#f97316"Any CSS color used for input focus outlines.

Frequently asked questions

How do I install the ROI calculator?

Copy the embed snippet and paste it where you want the calculator to appear in your page HTML (or in your platform's custom code block). The script renders the calculator inline at that exact spot — no other setup needed.

How do I pre-fill the amounts or change the currency?

Everything is controlled through data-hl-* attributes on the script tag. Set data-hl-invested, data-hl-returned, and data-hl-years to pre-fill the inputs, data-hl-currency for the currency code, and data-hl-accent for the highlight color. Visitors can edit any field and results update live.

Does it work on WordPress, Shopify, or Squarespace?

Yes. The widget is a single dependency-free vanilla JS file, so it runs anywhere you can paste a script tag — WordPress (custom HTML block), Shopify (theme.liquid or a custom section), Squarespace (code block), Webflow, or a plain HTML site.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch