Finance & Investing

Free Compound Interest Calculator Widget

Show visitors how their money grows with a live compound interest calculator.

A free compound interest calculator widget that computes final balance, total contributions, and interest earned, with a live growth curve drawn on canvas. Perfect for finance blogs, advisor sites, and savings tools. Install by pasting one script tag wherever you want the calculator to appear.

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/compound-interest-calculator.js" async></script>
<!-- Free Compound Interest Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/compound-interest-calculator -->
Inline (self-contained)html
<!-- Free Compound Interest Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/compound-interest-calculator -->
<script>
/*!
 * haynes lab — Compound Interest Calculator widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/compound-interest-calculator
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-compound-interest-calculator';
  var STYLE_ID = 'hlw-compound-interest-calculator-styles';

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 640px; margin: 0 auto; padding: 24px 24px 32px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 15px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }' +
      '.' + PREFIX + '__field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }' +
      '.' + PREFIX + '__field--full { grid-column: 1 / -1; }' +
      '.' + PREFIX + '__label { font-size: 12px; font-weight: 600; color: #6b7280;' +
      ' text-transform: uppercase; letter-spacing: 0.04em; }' +
      '.' + PREFIX + '__input { width: 100%; padding: 8px 10px; font: inherit; color: #111827;' +
      ' background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; }' +
      '.' + PREFIX + '__input:focus { outline: 2px solid var(--hlw-accent); outline-offset: 1px;' +
      ' border-color: var(--hlw-accent); background: #ffffff; }' +
      '.' + 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: 18px; font-weight: 700; color: #111827;' +
      ' word-break: break-word; }' +
      '.' + PREFIX + '__stat--balance .' + PREFIX + '__stat-value { color: var(--hlw-accent); font-size: 22px; }' +
      '.' + PREFIX + '__stat-label { display: block; font-size: 11px; color: #6b7280; margin-top: 2px;' +
      ' text-transform: uppercase; letter-spacing: 0.04em; }' +
      '.' + PREFIX + '__chart { display: block; width: 100%; height: 180px; margin-top: 18px; }' +
      '.' + 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: 560px) { .' + PREFIX + ' { padding: 18px 16px 30px; }' +
      ' .' + 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 numAttr(script, name, fallback) {
    var n = parseFloat(script.getAttribute(name));
    return isNaN(n) ? fallback : n;
  }

  function build(script) {
    var defaults = {
      principal: numAttr(script, 'data-hl-principal', 10000),
      rate: numAttr(script, 'data-hl-rate', 7),
      years: numAttr(script, 'data-hl-years', 20),
      compounds: numAttr(script, 'data-hl-compounds', 12),
      contribution: numAttr(script, 'data-hl-contribution', 200)
    };
    var currency = script.getAttribute('data-hl-currency') || 'USD';
    var accent = script.getAttribute('data-hl-accent') || '#f97316';

    var fmt;
    try {
      fmt = new Intl.NumberFormat(undefined, { style: 'currency', currency: currency, maximumFractionDigits: 0 });
    } catch (e) {
      console.warn('[haynes lab compound-interest-calculator] Unknown currency "' + currency + '", falling back to USD.');
      fmt = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
    }

    injectStyles();

    var root = el('div', PREFIX);
    root.style.setProperty('--hlw-accent', accent);

    var grid = el('div', PREFIX + '__grid');
    var inputs = {};
    var fields = [
      { key: 'principal', label: 'Initial principal', min: '0', step: 'any' },
      { key: 'rate', label: 'Annual rate (%)', min: '0', step: 'any' },
      { key: 'years', label: 'Years', min: '1', step: '1' },
      { key: 'compounds', label: 'Compounds per year', min: '1', step: '1' },
      { key: 'contribution', label: 'Monthly contribution', min: '0', step: 'any' }
    ];
    fields.forEach(function (f) {
      var wrap = el('div', PREFIX + '__field' + (f.key === 'contribution' ? ' ' + PREFIX + '__field--full' : ''));
      var id = PREFIX + '-' + f.key + '-' + Math.random().toString(36).slice(2, 8);
      var label = el('label', PREFIX + '__label', f.label);
      label.setAttribute('for', id);
      var input = el('input', PREFIX + '__input');
      input.type = 'number';
      input.id = id;
      input.min = f.min;
      input.step = f.step;
      input.value = String(defaults[f.key]);
      wrap.appendChild(label);
      wrap.appendChild(input);
      grid.appendChild(wrap);
      inputs[f.key] = input;
    });
    root.appendChild(grid);

    var results = el('div', PREFIX + '__results');
    results.setAttribute('aria-live', 'polite');
    var stats = {};
    [['balance', 'Final balance', true], ['contributed', 'Total contributed', false], ['interest', 'Interest earned', false]]
      .forEach(function (s) {
        var box = el('div', PREFIX + '__stat' + (s[2] ? ' ' + PREFIX + '__stat--balance' : ''));
        var value = el('span', PREFIX + '__stat-value', '—');
        box.appendChild(value);
        box.appendChild(el('span', PREFIX + '__stat-label', s[1]));
        results.appendChild(box);
        stats[s[0]] = value;
      });
    root.appendChild(results);

    var canvas = el('canvas', PREFIX + '__chart');
    canvas.setAttribute('role', 'img');
    canvas.setAttribute('aria-label', 'Growth curve of balance over time');
    root.appendChild(canvas);

    function readInputs() {
      var v = {};
      for (var key in inputs) {
        var n = parseFloat(inputs[key].value);
        v[key] = isNaN(n) || n < 0 ? 0 : n;
      }
      if (v.years < 1) v.years = 1;
      if (v.compounds < 1) v.compounds = 1;
      v.years = Math.min(Math.floor(v.years), 100);
      v.compounds = Math.min(Math.floor(v.compounds), 365);
      return v;
    }

    // Yearly balances. Monthly contributions earn the effective monthly rate
    // implied by the nominal annual rate compounded v.compounds times a year.
    function series(v) {
      var monthlyRate = Math.pow(1 + (v.rate / 100) / v.compounds, v.compounds / 12) - 1;
      var points = [v.principal];
      var balance = v.principal;
      for (var y = 1; y <= v.years; y++) {
        for (var m = 0; m < 12; m++) {
          balance = balance * (1 + monthlyRate) + v.contribution;
        }
        points.push(balance);
      }
      return points;
    }

    function draw(points, v) {
      var dpr = window.devicePixelRatio || 1;
      var width = canvas.clientWidth || 560;
      var height = 180;
      canvas.width = width * dpr;
      canvas.height = height * dpr;
      var ctx = canvas.getContext('2d');
      if (!ctx) return;
      ctx.scale(dpr, dpr);
      ctx.clearRect(0, 0, width, height);

      var padL = 8, padR = 8, padT = 10, padB = 18;
      var plotW = width - padL - padR;
      var plotH = height - padT - padB;
      var max = Math.max.apply(null, points.concat([1]));
      var invested = v.principal + v.contribution * 12 * v.years;

      function x(i) { return padL + (i / (points.length - 1 || 1)) * plotW; }
      function y(val) { return padT + plotH - (val / max) * plotH; }

      // gridlines
      ctx.strokeStyle = '#e5e7eb';
      ctx.lineWidth = 1;
      for (var g = 1; g <= 3; g++) {
        var gy = padT + (g / 4) * plotH;
        ctx.beginPath();
        ctx.moveTo(padL, gy);
        ctx.lineTo(width - padR, gy);
        ctx.stroke();
      }

      // invested principal line (dashed)
      ctx.strokeStyle = '#9ca3af';
      ctx.setLineDash([4, 4]);
      ctx.beginPath();
      ctx.moveTo(padL, y(invested > 0 ? Math.min(invested, max) : 0));
      ctx.lineTo(width - padR, y(invested > 0 ? Math.min(invested, max) : 0));
      ctx.stroke();
      ctx.setLineDash([]);

      // area fill + growth curve
      ctx.beginPath();
      ctx.moveTo(x(0), y(points[0]));
      for (var i = 1; i < points.length; i++) ctx.lineTo(x(i), y(points[i]));
      ctx.strokeStyle = accent;
      ctx.lineWidth = 2;
      ctx.stroke();
      ctx.lineTo(x(points.length - 1), y(0));
      ctx.lineTo(x(0), y(0));
      ctx.closePath();
      ctx.globalAlpha = 0.12;
      ctx.fillStyle = accent;
      ctx.fill();
      ctx.globalAlpha = 1;

      // axis labels
      ctx.fillStyle = '#6b7280';
      ctx.font = '11px system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
      ctx.textAlign = 'left';
      ctx.fillText('Year 0', padL, height - 5);
      ctx.textAlign = 'right';
      ctx.fillText('Year ' + v.years, width - padR, height - 5);
    }

    function update() {
      var v = readInputs();
      var points = series(v);
      var finalBalance = points[points.length - 1];
      var contributed = v.principal + v.contribution * 12 * v.years;
      stats.balance.textContent = fmt.format(finalBalance);
      stats.contributed.textContent = fmt.format(contributed);
      stats.interest.textContent = fmt.format(Math.max(finalBalance - contributed, 0));
      draw(points, v);
    }

    for (var key in inputs) {
      inputs[key].addEventListener('input', update);
    }
    window.addEventListener('resize', 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/compound-interest-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 compound-interest-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-principal"10000"Starting investment amount pre-filled in the calculator.
data-hl-rate"7"Annual interest rate in percent pre-filled in the calculator.
data-hl-years"20"Number of years to grow the investment, pre-filled in the calculator.
data-hl-compounds"12"How many times per year interest compounds (e.g. 12 for monthly, 1 for annually).
data-hl-contribution"200"Monthly contribution amount pre-filled in the calculator.
data-hl-currency"USD"ISO 4217 currency code used to format all money values.
data-hl-accent"#f97316"Hex color for the final balance, chart line, and focus states.

Frequently asked questions

How do I install the compound interest calculator?

Paste the script tag snippet into your page HTML where you want the calculator to appear. It renders inline right after the tag, pre-filled with your default principal, rate, years, compounding frequency, and monthly contribution.

Can I customize the defaults and colors?

Yes. Use data-hl-principal, data-hl-rate, data-hl-years, data-hl-compounds, and data-hl-contribution to set the pre-filled values, data-hl-currency to change the currency formatting (any ISO 4217 code like EUR or GBP), and data-hl-accent to match the chart and highlight color to your brand.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JavaScript file with no dependencies, so it works anywhere you can paste a script tag, including WordPress custom HTML blocks, Shopify pages, Squarespace code blocks, and plain HTML sites.

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