Down Payment Calculator

Professional down payment calculator for homebuyers. Instantly compute down payment amount or percent, loan amount, LTV, monthly payment (P&I), estimated closing costs, PMI threshold, and time needed to save. WCAG 2.1 AA accessible and mobile-first.

Down payment planner

Toggle between percent and dollar mode to understand your cash requirement, loan balance, and saving timeline.

How it estimates cash to close:

Down payment amount + closing costs percent × home price. Savings horizon assumes straight-line accumulation.

How to Use This Calculator

  1. Enter the estimated home price and select the loan product you are targeting.
  2. Switch between percent or amount mode and type the down payment you plan to make.
  3. Provide your interest rate, term, projected closing costs, and current/recurring savings.
  4. Click Calculate to refresh the results card, inspect cash-to-close, PMI odds, and saving timeline.
  5. The calculator shows how much you still need to save if your monthly contributions stay constant.

Methodology

The tool derives the down payment as either a percent of price or a direct dollar amount, then subtracts that value from the home price to compute the loan amount. The monthly principal & interest payment uses the fixed-rate amortization formula with the input APR and term.

Closing costs are estimated by applying the chosen rate to the purchase price, and cash-to-close sums the down payment plus those fees. Program minimums reflect common lender rules (Conventional ≥3%, FHA ≥3.5%, VA/USDA ≥0%), while PMI is flagged when the LTV exceeds 80% on conventional programs. Savings time-to-go assumes straight-line deposits without investment returns.

Formulas

Down payment and loan math:

Down payment (amount), when percent mode is used:
\( DP_{amt} = P \times \dfrac{DP\%}{100} \)
Loan amount: \( L = P - DP_{amt} \)
LTV: \( LTV\% = \dfrac{L}{P} \times 100 \)
Monthly P&I (amortized): \( M = L \cdot \dfrac{i(1+i)^n}{(1+i)^n - 1} \), where \( i = APR/12 \), \( n = 12 \cdot years \)
Closing costs estimate: \( C = P \times \dfrac{c}{100} \)
Cash to close (excl. credits/prepaids): \( Cash = DP_{amt} + C \)
Months to save: \( months = \left\lceil \max\left(0, \dfrac{DP_{amt} - S_{current}}{S_{monthly}} \right) \right\rceil \)

Variables referenced above:

  • T = property tax (annual or monthly depending on input)
  • I = homeowners insurance (annual or monthly depending on input)
  • PMI = private mortgage insurance (monthly)
  • HOA = homeowners association dues (monthly)

These references originate from the audited notes carried over from the legacy interface.

Citations
Changelog

Version 0.1.0-draft — 2026-01-19

  • Initial audit spec draft generated from HTML extraction (review required).
  • Verify formulas match the calculator engine and convert any text-only formulas to LaTeX.
  • Confirm sources are authoritative and relevant to the calculator methodology.
Verified by Ugo Candido Last Updated: 2026-01-19 Version 0.1.0-draft
Version 1.5.0
+ roundTo(safe).toFixed(2); } }; const formatPercent = (value) => { const safe = Number.isFinite(value) ? value : 0; const oneDecimal = Math.round((safe + Number.EPSILON) * 10) / 10; return oneDecimal.toLocaleString(undefined, { maximumFractionDigits: 1 }) + '%'; }; const formatTime = (months) => { if (!Number.isFinite(months)) return '—'; if (months <= 0) return '0 months'; return `${months} months`; }; function syncModeUI(updateValue = false) { if (els.modePercent.checked) { els.dpLabel.textContent = 'Down payment (%) *'; els.downPaymentInput.step = '0.1'; els.downPaymentInput.placeholder = '15'; els.downPaymentInput.value = updateValue && !els.downPaymentInput.value ? '15' : els.downPaymentInput.value; els.downPaymentInput.setAttribute('inputmode', 'decimal'); } else { els.dpLabel.textContent = 'Down payment (amount) *'; els.downPaymentInput.step = '100'; els.downPaymentInput.placeholder = '60000'; els.downPaymentInput.value = updateValue && !els.downPaymentInput.value ? '60000' : els.downPaymentInput.value; els.downPaymentInput.setAttribute('inputmode', 'numeric'); } } function parseInputs() { const parsedDown = parseFloat(els.downPaymentInput.value); return { price: parseFloat(els.price.value), downPaymentValue: Number.isFinite(parsedDown) ? parsedDown : 0, loanType: els.loanType.value, rate: parseFloat(els.rate.value), term: parseFloat(els.term.value), closingRate: parseFloat(els.closingRate.value), currentSavings: parseFloat(els.currentSavings.value), monthlySaving: parseFloat(els.monthlySaving.value), mode: els.modePercent.checked ? 'percent' : 'amount' }; } function validate(inputs) { const errors = []; if (!Number.isFinite(inputs.price) || inputs.price <= 0) errors.push('Home price must be greater than 0.'); if (!Number.isFinite(inputs.downPaymentValue) || inputs.downPaymentValue < 0) errors.push('Down payment must be 0 or greater.'); if (!Number.isFinite(inputs.rate) || inputs.rate < 0) errors.push('Interest rate must be 0 or greater.'); if (!Number.isFinite(inputs.term) || inputs.term <= 0) errors.push('Term must be greater than 0 years.'); if (!Number.isFinite(inputs.closingRate) || inputs.closingRate < 0) errors.push('Closing cost rate must be 0 or greater.'); if (!Number.isFinite(inputs.currentSavings) || inputs.currentSavings < 0) errors.push('Current savings must be 0 or greater.'); if (!Number.isFinite(inputs.monthlySaving) || inputs.monthlySaving < 0) errors.push('Monthly savings must be 0 or greater.'); const allowed = new Set(['conventional', 'fha', 'va', 'usda']); if (!allowed.has(inputs.loanType)) errors.push('Loan type is invalid.'); return { ok: errors.length === 0, errors }; } function amortPI(L, apr, years) { if (!Number.isFinite(L) || !Number.isFinite(apr) || !Number.isFinite(years)) return 0; const monthlyRate = apr / 100 / 12; const totalPayments = years * 12; if (L <= 0 || totalPayments <= 0) return 0; if (monthlyRate === 0) return L / totalPayments; const factor = Math.pow(1 + monthlyRate, totalPayments); return L * (monthlyRate * factor) / (factor - 1); } function programMinOk(loanType, dpPct) { if (loanType === 'fha') return dpPct >= 3.5; if (loanType === 'conventional') return dpPct >= 3.0; if (loanType === 'va' || loanType === 'usda') return dpPct >= 0; return true; } function pmiLikely(loanType, ltv) { if (loanType === 'conventional') return ltv > 80; return false; } function compute(inputs) { const isPercent = inputs.mode === 'percent'; let dpPct = 0; let dpAmt = 0; if (isPercent) { dpPct = inputs.downPaymentValue; dpAmt = inputs.price * (dpPct / 100); } else { dpAmt = inputs.downPaymentValue; dpPct = inputs.price > 0 ? (dpAmt / inputs.price) * 100 : 0; } dpPct = Math.max(0, dpPct); dpAmt = Math.max(0, dpAmt); const loanAmount = Math.max(0, inputs.price - dpAmt); const ltv = inputs.price > 0 ? (loanAmount / inputs.price) * 100 : 0; const monthlyPI = amortPI(loanAmount, inputs.rate, inputs.term); const closingCosts = inputs.price * (inputs.closingRate / 100); const cashToClose = dpAmt + closingCosts; const meetsProgram = programMinOk(inputs.loanType, dpPct); const needsPmi = pmiLikely(inputs.loanType, ltv); let monthsToSave = null; if (Number.isFinite(inputs.monthlySaving) && inputs.monthlySaving > 0 && dpAmt > 0) { const remain = Math.max(0, dpAmt - (Number.isFinite(inputs.currentSavings) ? inputs.currentSavings : 0)); monthsToSave = remain <= 0 ? 0 : Math.ceil(remain / inputs.monthlySaving); } return { downPaymentAmount: roundTo(dpAmt), downPaymentPercent: roundTo(dpPct, 1), loanAmount: roundTo(loanAmount), ltv: roundTo(ltv, 1), monthlyPI: roundTo(monthlyPI), closingCosts: roundTo(closingCosts), cashToClose: roundTo(cashToClose), needsPmi, meetsProgram, monthsToSave }; } function formatOutputs(outputs) { return { downPaymentAmount: formatCurrency(outputs.downPaymentAmount), downPaymentPercent: formatPercent(outputs.downPaymentPercent), loanAmount: formatCurrency(outputs.loanAmount), ltv: formatPercent(outputs.ltv), monthlyPI: formatCurrency(outputs.monthlyPI), closingCosts: formatCurrency(outputs.closingCosts), cashToClose: formatCurrency(outputs.cashToClose), needsPmi: outputs.needsPmi ? 'Likely' : 'Not likely', meetsProgram: outputs.meetsProgram ? 'Yes' : 'No', monthsToSave: outputs.monthsToSave === null ? '—' : formatTime(outputs.monthsToSave) }; } function render(formatted, errors = []) { if (!errors.length) { els.errorBox.style.display = 'none'; els.errorBox.textContent = ''; } else { els.errorBox.style.display = 'block'; els.errorBox.textContent = errors.join(' '); } if (!formatted) return; els.primaryResult.textContent = formatted.downPaymentAmount; els.resultDpPercent.textContent = formatted.downPaymentPercent; els.resultLoanAmount.textContent = formatted.loanAmount; els.resultLtv.textContent = formatted.ltv; els.resultMonthly.textContent = formatted.monthlyPI; els.resultClosing.textContent = formatted.closingCosts; els.resultCash.textContent = formatted.cashToClose; els.resultPmi.textContent = formatted.needsPmi; els.resultProgram.textContent = formatted.meetsProgram; els.resultTime.textContent = formatted.monthsToSave; } function update() { const inputs = parseInputs(); const validation = validate(inputs); if (!validation.ok) { render(null, validation.errors); return; } const outputs = compute(inputs); if (!outputs) { render(null, ['Unable to compute with current inputs.']); return; } render(formatOutputs(outputs)); } const debouncedUpdate = (function () { let timer; return function () { clearTimeout(timer); timer = setTimeout(update, 100); }; })(); function resetInputs() { els.price.value = defaultValues.price; els.rate.value = defaultValues.rate; els.term.value = defaultValues.term; els.closingRate.value = defaultValues.closingRate; els.currentSavings.value = defaultValues.currentSavings; els.monthlySaving.value = defaultValues.monthlySaving; els.loanType.value = defaultValues.loanType; els.modePercent.checked = true; els.modeAmount.checked = false; els.downPaymentInput.value = defaultValues.downPayment; syncModeUI(); const debouncedUpdate = debounce(update, 100); document.querySelectorAll('#inputsCard input, #inputsCard select, #inputsCard textarea') .forEach((el) => { el.addEventListener('input', debouncedUpdate); el.addEventListener('change', debouncedUpdate); }); update(); } element.addEventListener('change', debouncedUpdate); }); els.modePercent.addEventListener('change', () => { syncModeUI(true); update(); }); els.modeAmount.addEventListener('change', () => { syncModeUI(true); update(); }); els.calcBtn.addEventListener('click', update); els.resetBtn.addEventListener('click', resetInputs); syncModeUI(true); update(); })();