2023 Application

Overview

TaxGlobe is a client-side web application that calculates Canadian income tax for a given gross income and province. The user enters their annual income and selects a province from a dropdown; the application calculates both federal and provincial taxes by iterating through 2023 marginal tax bracket data, producing a full breakdown showing: tax owed in each bracket, total federal tax, total provincial tax, combined tax, effective tax rate (as a percentage of gross income), and net take-home income. All calculation logic runs in the browser with no server round-trip. Results update in real time as the user types or changes the province selection, with floating-point currency values rounded correctly to two decimal places using Math.round arithmetic to avoid the binary representation errors that affect naive floating-point addition.

Architecture

graph TD subgraph Input["User Input"] A["Income Input\nnumber field"] B["Province Select\ndropdown"] end subgraph Engine["JavaScript Tax Engine"] C["Federal Bracket Loop\n2023 CRA brackets\nMarginal rate accumulation"] D["Provincial Bracket Loop\nPer-province bracket data\nMarginal rate accumulation"] E["Totals Calculator\nTotal tax · Effective rate\nNet income"] end subgraph Output["Results Display"] F["Federal Breakdown Table\nBracket · Rate · Tax Owed"] G["Provincial Breakdown Table\nBracket · Rate · Tax Owed"] H["Summary Card\nTotal Tax · Effective Rate · Net Income"] end A --> C A --> D B --> D C --> E D --> E C --> F D --> G E --> H style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style C fill:#181818,stroke:#1e1e1e,color:#888 style D fill:#181818,stroke:#1e1e1e,color:#888 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style G fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style H fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0

Tech Stack

  • Vanilla JavaScript — Complete tax calculation engine; bracket data encoded as JS objects; real-time DOM updates via addEventListener('input')
  • 2023 Canadian Tax Bracket Data — Federal CRA brackets and all provincial bracket tables encoded as arrays of { min, max, rate } objects
  • HTML5 — Form with type="number" income input and province <select>; results rendered into a structured table and summary card
  • CSS3 — Clean card layout for results; responsive grid for the breakdown tables; colour-coded effective rate display

Build Process

1
Research & Encode 2023 Tax Bracket Data

The 2023 federal and provincial tax brackets were researched from CRA publications and encoded as JavaScript data objects. Each bracket entry specifies the lower bound, upper bound (or Infinity for the top bracket), and the marginal rate as a decimal.

// taxData.js — 2023 Canadian federal brackets
const federalBrackets = [
  { min: 0,       max: 53359,   rate: 0.15   },
  { min: 53359,   max: 106717,  rate: 0.205  },
  { min: 106717,  max: 165430,  rate: 0.26   },
  { min: 165430,  max: 235675,  rate: 0.29   },
  { min: 235675,  max: Infinity, rate: 0.33  },
];

// Ontario provincial brackets (example)
const ontarioBrackets = [
  { min: 0,       max: 49231,   rate: 0.0505 },
  { min: 49231,   max: 98463,   rate: 0.0915 },
  { min: 98463,   max: 150000,  rate: 0.1116 },
  { min: 150000,  max: 220000,  rate: 0.1216 },
  { min: 220000,  max: Infinity, rate: 0.1316 },
];
2
Marginal Tax Calculation Engine

The core calculation function iterates through the bracket array. For each bracket, it computes the taxable amount within that bracket — the lesser of the income above the bracket floor and the bracket width — and multiplies by the marginal rate. Accumulated totals across all brackets give the total tax for that bracket set.

function calcTax(income, brackets) {
  let totalTax = 0;
  const breakdown = [];

  for (const bracket of brackets) {
    if (income <= bracket.min) break;
    const taxableInBracket = Math.min(income, bracket.max) - bracket.min;
    const taxInBracket = taxableInBracket * bracket.rate;
    totalTax += taxInBracket;
    breakdown.push({
      min:    bracket.min,
      max:    bracket.max,
      rate:   bracket.rate,
      taxable: taxableInBracket,
      tax:    taxInBracket,
    });
  }

  return { totalTax, breakdown };
}
3
Effective Rate & Net Income Calculation

After running both the federal and provincial calculations, totals are combined and derived figures computed. Floating-point values are rounded to cents using Math.round(value * 100) / 100 before display, preventing binary representation artefacts from showing incorrect cent values in the output.

function calcSummary(income, province) {
  const fed   = calcTax(income, federalBrackets);
  const prov  = calcTax(income, provinceBrackets[province]);
  const total = fed.totalTax + prov.totalTax;
  const round = v => Math.round(v * 100) / 100;
  return {
    federalTax:    round(fed.totalTax),
    provincialTax: round(prov.totalTax),
    totalTax:      round(total),
    effectiveRate: round((total / income) * 100),
    netIncome:     round(income - total),
    fedBreakdown:  fed.breakdown,
    provBreakdown: prov.breakdown,
  };
}
4
HTML Form: Income Input & Province Select

The form uses a type="number" input with min="0" and step="1" for the income field. The province dropdown is populated from the keys of the bracket data object, so adding a new province's data automatically adds it to the dropdown without modifying the HTML. Both inputs use required for HTML5 validation.

5
CSS Results Card & Breakdown Table

Results are displayed in two sections: a summary card showing the headline figures (total tax, effective rate, net income) and two collapsible breakdown tables showing the per-bracket calculation for federal and provincial tax respectively. The effective rate is colour-coded — green for rates under 20%, amber for 20–30%, red for above 30%.

6
Real-Time Calculation on Input

Both the income input and province select listen for input events. On each event, the calculation runs and the DOM is updated immediately — no submit button required. Input debouncing with a short timeout prevents excessive recalculation during rapid typing.

let debounceTimer;
[incomeInput, provinceSelect].forEach(el => {
  el.addEventListener('input', () => {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
      const income = parseFloat(incomeInput.value);
      const province = provinceSelect.value;
      if (!isNaN(income) && income > 0 && province) {
        renderResults(calcSummary(income, province));
      }
    }, 150);
  });
});

Calculation Flow

flowchart LR A["User Enters Income\n+ Selects Province"] --> B["input event fires\n150ms debounce"] B --> C["calcTax(income, federalBrackets)\nIterate brackets · Accumulate"] B --> D["calcTax(income, provinceBrackets[province])\nIterate brackets · Accumulate"] C --> E["Federal Total Tax\n+ Breakdown Array"] D --> F["Provincial Total Tax\n+ Breakdown Array"] E --> G["calcSummary()\nCombine totals\nMath.round to cents"] F --> G G --> H["renderResults()\nUpdate DOM"] H --> I["Summary Card\nTotal · Effective Rate · Net"] H --> J["Federal Breakdown Table\nPer-bracket rows"] H --> K["Provincial Breakdown Table\nPer-bracket rows"] style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#181818,stroke:#1e1e1e,color:#888 style C fill:#181818,stroke:#1e1e1e,color:#888 style D fill:#181818,stroke:#1e1e1e,color:#888 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#181818,stroke:#1e1e1e,color:#888 style H fill:#181818,stroke:#1e1e1e,color:#888 style I fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style J fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style K fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0

Challenges & Solutions

Floating-Point Precision in Currency Calculations. JavaScript's IEEE 754 floating-point arithmetic causes notorious precision errors in currency calculations. For example, 0.1 + 0.2 produces 0.30000000000000004 in JavaScript. In a tax calculator, these errors accumulate across bracket additions and can produce results like $15,234.999999 instead of $15,235.00. The solution was to apply Math.round(value * 100) / 100 as a final step on all currency output values — this rounds to the nearest cent before display. For summing bracket tax values, the accumulation uses raw floats (where the error is negligible per bracket) and only rounds at the final output stage.

Keeping Tax Bracket Data Accurate. Tax brackets change annually. Encoding them as static JavaScript data means the application will become stale after each tax year. The data structure was designed to make updates straightforward: each jurisdiction is a named key in the brackets object, and each bracket is a simple { min, max, rate } object that mirrors the CRA table format directly. Adding or updating a province requires only editing the data object, not touching any calculation logic. A // DATA VERSION: 2023 comment is placed prominently at the top of the data file to make staleness immediately visible.

What I Learned

  • How Canadian marginal tax brackets work in practice: income is taxed at each bracket's rate only on the portion that falls within that bracket, not on the entire income
  • Effective tax rate (total tax divided by gross income) versus marginal rate (the rate applied to the last dollar earned) — a common source of public confusion that good UI can clarify
  • IEEE 754 floating-point precision errors are real and unavoidable in JavaScript currency arithmetic; the correct mitigation is Math.round(v * 100) / 100 at output time, not during accumulation
  • Input debouncing with setTimeout/clearTimeout prevents expensive operations from running on every keystroke and is the standard pattern for real-time input-driven computation
  • Encoding structured data (tax brackets) as clean JavaScript objects rather than inline HTML makes the data easy to update, test, and eventually replace with an API call
  • Separating calculation logic from rendering logic (pure calcSummary() function vs renderResults() function) makes both independently testable and reusable
JavaScript HTML5 CSS3 Tax Calculator Financial App Vanilla JS