When you get a W-2, the government takes its cut before you ever see the money. Social Security, Medicare, all of it, handled by payroll software. When you're self-employed, you get to do that math yourself, and the "15.3% self-employment tax" everyone quotes is quietly wrong in three ways. It's 15.3% of 92.35% of your income, the Social Security half stops at a wage cap, and if you earn enough, a 0.9% surtax kicks in on top.
I built a calculator that gets all three right in a single HTML file. No frameworks, no build step, no CDN, no backend. Here's how the math works and the code behind it.
The tax formula challenge
Self-employment tax is the self-employed version of FICA: 12.4% for Social Security plus 2.9% for Medicare. Three details turn "multiply by 15.3%" into actual logic:
- The 92.35% adjustment. Because the employer half of the tax is deductible, you only pay SE tax on 92.35% of your net income (IRC §1402(a)(12)).
- The Social Security wage base. The 12.4% portion only applies up to a cap: $184,500 for 2026, $176,100 for 2025. And here's the part most calculators miss: W-2 wages count against that cap too.
- The Additional Medicare surtax. Above $200,000 (single filers), an extra 0.9% applies. The threshold isn't indexed, so it catches more people every year.
Get any of these wrong and you're off by real money. Most free calculators I tried do "income × 15.3%" and stop. This is the SE tax layer only. Income tax, state tax, and the qualified business income deduction are separate problems, and deliberately out of scope.
Rates as data, not magic numbers
The first decision was to stop hardcoding numbers inside the logic. Tax rates change every year, so they live in one object, one entry per year:
// 2026 rates (source: tax-formula-reference.md)
const RATES = {
'2026': { ssBase: 184500, ssRate: 0.124, medRate: 0.029, extraRate: 0.009, extraThreshold: 200000 },
'2025': { ssBase: 176100, ssRate: 0.124, medRate: 0.029, extraRate: 0.009, extraThreshold: 200000 },
'2024': { ssBase: 168600, ssRate: 0.124, medRate: 0.029, extraRate: 0.009, extraThreshold: 200000 }
};
const ADJUSTMENT = 0.9235; // IRC §1402(a)(12)
const DEDUCTION_RATE = 0.5; // IRC §164(f)
Updating the tool next year means editing one object, not hunting through functions.
The core calculate() function
The whole product hinges on this function. Everything around it is a form, a results table, and some CSS.
function calculate() {
const netIncome = parseFloat(document.getElementById('netIncome').value) || 0;
const w2Wages = parseFloat(document.getElementById('w2Wages').value) || 0;
const rates = RATES[document.getElementById('taxYear').value];
// Step 1: the 92.35% adjustment
const adjusted = netIncome * ADJUSTMENT;
// Step 2: Social Security, 12.4% up to the wage base.
// W-2 wages count against the base, so subtract them first.
const ssRemainingBase = Math.max(0, rates.ssBase - w2Wages);
const ssTaxable = Math.min(adjusted, ssRemainingBase);
const ssTax = ssTaxable * rates.ssRate;
// Step 3: Medicare, 2.9% on all adjusted income (no cap)
const medTax = adjusted * rates.medRate;
// Step 4: Additional Medicare, 0.9% above the $200K threshold
const extraTaxable = Math.max(0, netIncome - rates.extraThreshold);
const extraTax = extraTaxable * rates.extraRate;
const totalTax = ssTax + medTax + extraTax;
const deduction = totalTax * DEDUCTION_RATE; // 50% is deductible (IRC §164(f))
const effectiveRate = (totalTax / netIncome) * 100;
// ...then write everything to the DOM
}
The real function ends with the DOM updates and a quarterly-payment estimate. I trimmed those here to focus on the math. Four steps, four lines of arithmetic, two edge-case guards.
For $85,000 of net income in 2026: adjusted income is $78,497, Social Security tax is $9,733, Medicare is $2,276, total is $12,010, and the deductible half is $6,005. Effective rate: 14.13%, not the 15.3% you'll see quoted everywhere.
The edge case most calculators get wrong: W-2 wages
The pair of lines I'm most happy with:
const ssRemainingBase = Math.max(0, rates.ssBase - w2Wages);
const ssTaxable = Math.min(adjusted, ssRemainingBase);
If you're a freelancer with a day job, your W-2 Social Security tax doesn't reset just because you also have 1099 income. The $184,500 cap is shared between both. With 2026 numbers:
| Scenario | SS-taxable SE income |
|---|---|
| $85K SE income, no W-2 | $78,497 (the full 92.35% adjusted amount) |
| $50K SE income + $160K W-2 | $24,500 (only the room left under the cap) |
| $30K SE income + $200K W-2 | $0 (the cap is already used up) |
Without the Math.max(0, ...) guard, the second scenario would produce a negative base and a negative tax. Clamping to zero is what makes the third scenario correct: once your W-2 wages exceed the base, your self-employment Social Security tax is exactly $0. You still pay 2.9% Medicare on every dollar, because Medicare has no cap.
The same Math.max(0, ...) pattern handles the surtax: Math.max(0, netIncome - rates.extraThreshold) stays at zero until income crosses $200K, so the surtax row in the breakdown stays at $0 until it matters.
Lessons learned
- A calculator is only as good as the tax rules it encodes. The "15.3%" shortcut is wrong at every income level, just less wrong below the wage base. Read the IRS publication, then encode each adjustment separately.
- Edge cases are where the value is. The W-2 plus 1099 combination is extremely common, and it's the one thing every other free calculator I tried ignored.
- Zero dependencies is a feature. The page loads instantly, works offline, and "view source" doubles as your documentation. There is no bundle to debug, no dependency to audit.
-
Guard your divisions. If someone clears the income field,
totalTax / netIncomeis0 / 0, which renders asNaN%in the effective-rate card. In hindsight I'd clamp net income before computing the rate. -
Keep rates as data. The moment you hardcode
184500inside a function, someone will copy it into the wrong tax year.
The whole thing, from first draft to deployed page, took a few hours. Cost: $0.
Try it
The live tool is here: Self-Employment Tax Calculator
It's a single HTML file. Open the page, view source, and the entire implementation is right there, commented and readable. If you want the background on how SE tax actually works, there's a companion guide on the Inisyght blog.
Top comments (0)