Finance & Investing

Free Mortgage Calculator Widget

Let visitors estimate monthly mortgage payments right on your site.

A clean mortgage calculator your visitors can use to estimate monthly principal-and-interest payments, total interest, and total loan cost from home price, down payment, rate, and term. It recalculates live as they type, so it is perfect for real estate, lending, and finance sites. Install it with a single script tag — no dependencies, 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/mortgage-calculator.js" async></script>
<!-- Free Mortgage Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/mortgage-calculator -->
Inline (self-contained)html
<!-- Free Mortgage Calculator widget by haynes lab — https://hayneslab.dev/tools/widgets/mortgage-calculator -->
<script>
/*!
 * haynes lab — Mortgage Calculator widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/mortgage-calculator
 */
(function () {
  'use strict';

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

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 480px; 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: 1fr 1fr; gap: 12px; }' +
      '.' + PREFIX + '__field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }' +
      '.' + PREFIX + '__label { font-size: 12px; font-weight: 600; color: #6b7280; }' +
      '.' + PREFIX + '__input { width: 100%; padding: 8px 10px; font: inherit; 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 + '__results { margin-top: 18px; padding-top: 18px; border-top: 1px solid #e5e7eb;' +
      ' display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }' +
      '.' + PREFIX + '__result--hero { grid-column: 1 / -1; text-align: center; padding: 14px;' +
      ' background: #f9fafb; border-radius: 10px; }' +
      '.' + PREFIX + '__result-label { font-size: 12px; font-weight: 600; color: #6b7280;' +
      ' text-transform: uppercase; letter-spacing: 0.04em; }' +
      '.' + PREFIX + '__result-value { font-size: 18px; font-weight: 700; color: #111827; margin-top: 2px; }' +
      '.' + PREFIX + '__result--hero .' + PREFIX + '__result-value { font-size: 30px; color: var(--hlw-accent); }' +
      '.' + 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: 400px) { .' + 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) || n < 0) ? fallback : n;
  }

  function build(script) {
    var defaults = {
      price: numAttr(script, 'data-hl-price', 350000),
      down: numAttr(script, 'data-hl-down', 20),
      rate: numAttr(script, 'data-hl-rate', 6.5),
      term: numAttr(script, 'data-hl-term', 30)
    };
    var currency = (script.getAttribute('data-hl-currency') || 'USD').toUpperCase();
    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 mortgage-calculator] Invalid data-hl-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 uid = 'hlw-mc-' + Math.random().toString(36).slice(2, 8);

    function field(labelText, attr, value, step) {
      var wrap = el('div', PREFIX + '__field');
      var id = uid + '-' + attr;
      var label = el('label', PREFIX + '__label', labelText);
      label.setAttribute('for', id);
      var input = el('input', PREFIX + '__input');
      input.type = 'number';
      input.id = id;
      input.min = '0';
      if (step) input.step = step;
      input.value = String(value);
      input.setAttribute('data-hl-field', attr);
      wrap.appendChild(label);
      wrap.appendChild(input);
      grid.appendChild(wrap);
      return input;
    }

    var priceInput = field('Home price', 'price', defaults.price, '1000');
    var downInput = field('Down payment (%)', 'down', defaults.down, '1');
    var rateInput = field('Interest rate (%)', 'rate', defaults.rate, '0.05');
    var termInput = field('Term (years)', 'term', defaults.term, '1');
    root.appendChild(grid);

    var results = el('div', PREFIX + '__results');
    var hero = el('div', PREFIX + '__result ' + PREFIX + '__result--hero');
    hero.appendChild(el('div', PREFIX + '__result-label', 'Monthly payment (P&I)'));
    var paymentEl = el('div', PREFIX + '__result-value');
    paymentEl.setAttribute('aria-live', 'polite');
    hero.appendChild(paymentEl);
    results.appendChild(hero);

    function stat(labelText) {
      var wrap = el('div', PREFIX + '__result');
      wrap.appendChild(el('div', PREFIX + '__result-label', labelText));
      var value = el('div', PREFIX + '__result-value');
      wrap.appendChild(value);
      results.appendChild(wrap);
      return value;
    }
    var interestEl = stat('Total interest');
    var totalEl = stat('Total cost');
    root.appendChild(results);

    function update() {
      var price = parseFloat(priceInput.value);
      var downPct = parseFloat(downInput.value);
      var rate = parseFloat(rateInput.value);
      var years = parseFloat(termInput.value);
      if (isNaN(price) || price <= 0 || isNaN(downPct) || downPct < 0 || downPct >= 100 ||
          isNaN(rate) || rate < 0 || isNaN(years) || years <= 0) {
        paymentEl.textContent = '—';
        interestEl.textContent = '—';
        totalEl.textContent = '—';
        return;
      }
      var principal = price * (1 - downPct / 100);
      var n = years * 12;
      var r = rate / 100 / 12;
      var monthly = r > 0
        ? principal * r * Math.pow(1 + r, n) / (Math.pow(1 + r, n) - 1)
        : principal / n;
      var total = monthly * n;
      paymentEl.textContent = fmt.format(monthly);
      interestEl.textContent = fmt.format(total - principal);
      totalEl.textContent = fmt.format(total + (price - principal));
    }

    [priceInput, downInput, rateInput, termInput].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/mortgage-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 mortgage-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-price"350000"Initial home price shown in the calculator.
data-hl-down"20"Initial down payment as a percentage of the home price.
data-hl-rate"6.5"Initial annual interest rate (APR, percent).
data-hl-term"30"Initial loan term in years.
data-hl-currency"USD"ISO 4217 currency code used to format all amounts.
data-hl-accent"#f97316"Highlight color for the monthly payment and focus states.

Frequently asked questions

How do I install the Mortgage Calculator?

Paste the script snippet where you want the calculator to appear — it renders inline right after the tag and starts computing immediately with your default values.

Can I change the default loan values and colors?

Yes — set data-hl-price, data-hl-down, data-hl-rate, data-hl-term, and data-hl-currency for the starting values, and data-hl-accent for the highlight color.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies, so it works on WordPress, Shopify, Squarespace, or any site that can embed a script tag.

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