Tax-Efficient Budgeting Strategy Tool

Your Financial Snapshot & Goals

This amount will be allocated across accounts in Tab 3.
Before taxes and fees.

Your Estimated Tax Assumptions

Enter your best estimates for these rates. These are simplified and for illustrative purposes only. This tool does not provide tax advice.

Rate for deductions and taxing ordinary income withdrawals from tax-deferred accounts.
Average rate for capital gains, dividends in taxable accounts. Applied annually to growth in this model.
If blank, Marginal Income Tax Rate will be used.

Your Savings Allocation Strategy

Allocate your total annual savings (from Tab 1) across these generic account types. Percentages must sum to 100%.

E.g., Traditional 401(k)/IRA. Contributions may lower current taxable income. Withdrawals typically taxed as income.
E.g., Roth 401(k)/IRA, TFSA (Canada), ISA (UK). Qualified withdrawals are typically tax-free.
E.g., Brokerage account. Investment income (dividends, capital gains) typically taxed.
Total Allocated: 100%

Budgeting Impact & Projections

Scenario 1: Your Chosen Allocation Strategy

Account TypeTotal Contributed ($)Total Growth ($)Est. Tax Impact ($)End Value (Net) ($)

Scenario 2: Baseline (All Savings in Taxable Account)

Account TypeTotal Contributed ($)Total Growth ($)Est. Tax Impact ($)End Value (Net) ($)

Comparison & Key Takeaways

Your chosen strategy results in a projected net portfolio value of $${strategyNet.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}.

The baseline (all savings in taxable accounts) results in a projected net portfolio value of $${baselineNet.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}.

Your strategy is projected to be $${Math.abs(difference).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})} ${higherOrLower} than the baseline after ${inputs.horizon} years.

This simplified model illustrates potential impacts. Actual results will vary. Consult a professional.

`; comparisonTextEl.innerHTML = comparisonHTML; // Store for PDF window.tebs_pdfData = { inputs, strategyProjection, baselineProjection, comparisonHTML }; } function tebs_getInputs() { const grossIncome = parseFloat(grossIncomeEl.value); const annualSavings = parseFloat(annualSavingsEl.value); const horizon = parseInt(investmentHorizonEl.value); const growthRate = parseFloat(growthRateEl.value) / 100; const marginalTax = parseFloat(marginalTaxRateEl.value) / 100; const taxableGrowthTax = parseFloat(taxableGrowthRateEl.value) / 100; let retirementTax = parseFloat(retirementTaxRateEl.value) / 100; if (isNaN(retirementTax)) retirementTax = marginalTax; // Default to marginal if blank const allocations = { A: parseFloat(percentTypeAEl.value) / 100, B: parseFloat(percentTypeBEl.value) / 100, C: parseFloat(percentTypeCEl.value) / 100 }; if (isNaN(grossIncome) || isNaN(annualSavings) || isNaN(horizon) || isNaN(growthRate) || isNaN(marginalTax) || isNaN(taxableGrowthTax)) { projectionWarningEl.textContent = "Please ensure all numerical inputs in Tab 1 & 2 are valid numbers."; tebs_clearResults(); return null; } if (annualSavings > grossIncome) { projectionWarningEl.textContent = "Annual savings cannot exceed gross income."; tebs_clearResults(); return null; } return { grossIncome, annualSavings, horizon, growthRate, marginalTax, taxableGrowthTax, retirementTax, allocations }; } function tebs_clearResults() { strategyResultsEl.innerHTML = "

Enter valid inputs and allocate savings to see projections.

"; strategyTableBodyEl.innerHTML = ""; baselineResultsEl.innerHTML = "

Enter valid inputs and allocate savings to see projections.

"; baselineTableBodyEl.innerHTML = ""; comparisonTextEl.innerHTML = ""; } function tebs_projectScenario(inputs, scenarioName) { const { annualSavings, horizon, growthRate, marginalTax, taxableGrowthTax, retirementTax, allocations } = inputs; let valA = 0, valB = 0, valC = 0; let totalContribA = 0, totalContribB = 0, totalContribC = 0; let totalGrowthA = 0, totalGrowthB = 0, totalGrowthC = 0; let taxImpactA = 0, taxImpactB = 0, taxImpactC = 0; // Tax impact can be saving (+) or cost (-) const yearlyData = []; // For PDF year-by-year table for (let year = 1; year <= horizon; year++) { // Contributions for the year const contribA = annualSavings * allocations.A; const contribB = annualSavings * allocations.B; const contribC = annualSavings * allocations.C; totalContribA += contribA; totalContribB += contribB; totalContribC += contribC; // Account A: Tax-Deductible if (contribA > 0) { taxImpactA += contribA * marginalTax; // Immediate tax saving valA += contribA; } const growthA_thisYear = valA * growthRate; valA += growthA_thisYear; totalGrowthA += growthA_thisYear; // Account B: Post-Tax, Tax-Free Growth if (contribB > 0) { valB += contribB; // Contributions are post-tax (no immediate impact shown here beyond being from net income) } const growthB_thisYear = valB * growthRate; valB += growthB_thisYear; totalGrowthB += growthB_thisYear; // taxImpactB remains 0 (no tax on growth, no tax on withdrawal) // Account C: Taxable if (contribC > 0) { valC += contribC; // Contributions are post-tax } const growthC_thisYear = valC * growthRate; const taxOnGrowthC = growthC_thisYear * taxableGrowthTax; taxImpactC -= taxOnGrowthC; // Tax cost valC += (growthC_thisYear - taxOnGrowthC); // Growth is net of tax totalGrowthC += growthC_thisYear; // Gross growth before tax for reporting yearlyData.push({ year, valA_EoyNet: valA, // Pre-withdrawal tax for A valB_EoyNet: valB, // Already net valC_EoyNet: valC // Already net of annual growth tax }); } // Final net values after withdrawal taxes const finalNetA = valA * (1 - retirementTax); taxImpactA -= (valA * retirementTax); // Tax cost at withdrawal const finalNetB = valB; // No tax at withdrawal const finalNetC = valC; // Growth already taxed annually return { accounts: { A: { totalContrib: totalContribA, totalGrowth: totalGrowthA, taxImpact: taxImpactA, endValueNet: finalNetA, name: "Account A (Tax-Deductible)" }, B: { totalContrib: totalContribB, totalGrowth: totalGrowthB, taxImpact: taxImpactB, endValueNet: finalNetB, name: "Account B (Tax-Free Growth)" }, C: { totalContrib: totalContribC, totalGrowth: totalGrowthC, taxImpact: taxImpactC, endValueNet: finalNetC, name: "Account C (Taxable)" } }, totalNetValue: finalNetA + finalNetB + finalNetC, totalContributions: totalContribA + totalContribB + totalContribC, totalTaxImpact: taxImpactA + taxImpactB + taxImpactC, yearlyData }; } function tebs_displayScenarioResults(projection, resultsEl, tableBodyEl, scenarioName) { resultsEl.innerHTML = `
Total Contributions ($) ${projection.totalContributions.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
Total Estimated Tax Impact ($) (Savings - Costs) ${projection.totalTaxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
Projected Total Net Value ($) (After ${projection.yearlyData.length} Years) ${projection.totalNetValue.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
`; tableBodyEl.innerHTML = ''; for (const type in projection.accounts) { const acc = projection.accounts[type]; if (scenarioName === "Baseline" && (type === "A" || type === "B")) continue; // Baseline only has C const row = tableBodyEl.insertRow(); row.insertCell().textContent = acc.name; row.insertCell().textContent = acc.totalContrib.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); row.insertCell().classList.add('tebs-currency'); row.insertCell().textContent = acc.totalGrowth.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); row.insertCell().classList.add('tebs-currency'); row.insertCell().textContent = acc.taxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); row.insertCell().classList.add('tebs-currency'); row.insertCell().textContent = acc.endValueNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); row.insertCell().classList.add('tebs-currency'); } } // --- PDF Generation --- downloadPdfBtnEl.addEventListener('click', function() { window.tebs_generatePdf(); }); window.tebs_generatePdf = function() { const pdfElement = document.getElementById('tebs-pdfOutput'); if (typeof html2pdf === 'undefined') { alert('PDF generation library is not loaded. Please check your internet connection.'); return; } if (!window.tebs_pdfData) { alert('No data to generate PDF. Please calculate projections first.'); return; } const { inputs, strategyProjection, baselineProjection, comparisonHTML } = window.tebs_pdfData; // Populate PDF Input Summary pdfInputSummaryEl.innerHTML = `
  • Annual Gross Income: $${inputs.grossIncome.toLocaleString()}
  • Annual Savings: $${inputs.annualSavings.toLocaleString()}
  • Investment Horizon: ${inputs.horizon} Years
  • Expected Growth Rate: ${(inputs.growthRate * 100).toFixed(1)}%
  • Marginal Tax Rate: ${(inputs.marginalTax * 100).toFixed(1)}%
  • Taxable Growth Tax Rate: ${(inputs.taxableGrowthTax * 100).toFixed(1)}%
  • Retirement Tax Rate: ${(inputs.retirementTax * 100).toFixed(1)}%
  • Allocations (A/B/C): ${(inputs.allocations.A * 100).toFixed(0)}% / ${(inputs.allocations.B * 100).toFixed(0)}% / ${(inputs.allocations.C * 100).toFixed(0)}%
  • `; // Populate PDF Strategy pdfStrategyResultsEl.innerHTML = ` Total Contributions: $${strategyProjection.totalContributions.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
    Total Est. Tax Impact: $${strategyProjection.totalTaxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
    Projected Net Value: $${strategyProjection.totalNetValue.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})} `; pdfStrategyTableBodyEl.innerHTML = ''; for (const type in strategyProjection.accounts) { const acc = strategyProjection.accounts[type]; const r = pdfStrategyTableBodyEl.insertRow(); r.insertCell().textContent = acc.name; r.insertCell().textContent = acc.totalContrib.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[1].className = 'tebs-currency'; r.insertCell().textContent = acc.totalGrowth.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[2].className = 'tebs-currency'; r.insertCell().textContent = acc.taxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[3].className = 'tebs-currency'; r.insertCell().textContent = acc.endValueNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[4].className = 'tebs-currency'; } pdfStrategyYearlyBodyEl.innerHTML = ''; strategyProjection.yearlyData.forEach(y => { const r = pdfStrategyYearlyBodyEl.insertRow(); r.insertCell().textContent = y.year; let totalYearNet = 0; // For PDF year-by-year, we need to apply final withdrawal tax to Account A for that year's snapshot if it were withdrawn const valA_year_net_of_final_tax = y.valA_EoyNet * (1 - inputs.retirementTax); totalYearNet += valA_year_net_of_final_tax + y.valB_EoyNet + y.valC_EoyNet; r.insertCell().textContent = valA_year_net_of_final_tax.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[1].className = 'tebs-currency'; r.insertCell().textContent = y.valB_EoyNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[2].className = 'tebs-currency'; r.insertCell().textContent = y.valC_EoyNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[3].className = 'tebs-currency'; r.insertCell().textContent = totalYearNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[4].className = 'tebs-currency'; }); // Populate PDF Baseline pdfBaselineResultsEl.innerHTML = ` Total Contributions: $${baselineProjection.totalContributions.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
    Total Est. Tax Impact: $${baselineProjection.totalTaxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})}
    Projected Net Value: $${baselineProjection.totalNetValue.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0})} `; pdfBaselineTableBodyEl.innerHTML = ''; const accC_baseline = baselineProjection.accounts.C; const r_bl = pdfBaselineTableBodyEl.insertRow(); r_bl.insertCell().textContent = accC_baseline.name; r_bl.insertCell().textContent = accC_baseline.totalContrib.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r_bl.cells[1].className = 'tebs-currency'; r_bl.insertCell().textContent = accC_baseline.totalGrowth.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r_bl.cells[2].className = 'tebs-currency'; r_bl.insertCell().textContent = accC_baseline.taxImpact.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r_bl.cells[3].className = 'tebs-currency'; r_bl.insertCell().textContent = accC_baseline.endValueNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r_bl.cells[4].className = 'tebs-currency'; pdfBaselineYearlyBodyEl.innerHTML = ''; baselineProjection.yearlyData.forEach(y => { const r = pdfBaselineYearlyBodyEl.insertRow(); r.insertCell().textContent = y.year; r.insertCell().textContent = y.valC_EoyNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[1].className = 'tebs-currency'; r.insertCell().textContent = y.valC_EoyNet.toLocaleString(undefined, {minimumFractionDigits:0, maximumFractionDigits:0}); r.cells[2].className = 'tebs-currency'; // Total is same as C for baseline }); pdfComparisonTextEl.innerHTML = comparisonHTML.replace(//g, '').replace(/<\/strong>/g, '').replace(//g, '(').replace(/<\/small>/g, ')'); // Basic tag stripping for PDF const filename = `Tax_Efficient_Budgeting_Projections_${new Date().toISOString().slice(0,10)}.pdf`; const opt = { margin: [0.4, 0.3, 0.6, 0.3], filename: filename, image: { type: 'jpeg', quality: 0.95 }, html2canvas: { scale: 2, useCORS: true, letterRendering: true, scrollY: 0 }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }, pagebreak: { mode: ['avoid-all', 'css', 'legacy'] } }; html2pdf().from(pdfElement).set(opt).save(); }; // --- Tab Logic --- window.tebs_openTab = function(event, tabId) { const tabContents = document.getElementsByClassName('tebs-tab-content'); Array.from(tabContents).forEach(tc => tc.classList.remove('tebs-active')); const tabButtons = document.getElementsByClassName('tebs-tab-button'); Array.from(tabButtons).forEach(tb => tb.classList.remove('tebs-active')); const targetTabContent = document.getElementById(tabId); if (targetTabContent) targetTabContent.classList.add('tebs-active'); else { console.error(`Tab content '${tabId}' not found.`); return; } if (event && event.currentTarget) event.currentTarget.classList.add('tebs-active'); else { // Programmatic: find and activate button Array.from(tabButtons).find(btn => btn.getAttribute('onclick')?.includes(`'${tabId}'`))?.classList.add('tebs-active'); } }; window.tebs_navigateToTab = function(tabId) { window.tebs_openTab(null, tabId); if (tabId === 'tebs-projectionsTab') { window.tebs_calculateAndDisplayProjections(); } }; // --- Initial Setup --- window.tebs_openTab(null, 'tebs-profileTab'); });
    Scroll to Top