<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Monu Kumar</title>
    <description>The latest articles on DEV Community by Monu Kumar (@monu_kumar).</description>
    <link>https://dev.to/monu_kumar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3847017%2F9af07998-7dfa-4fc3-87c5-54c6f278a998.png</url>
      <title>DEV Community: Monu Kumar</title>
      <link>https://dev.to/monu_kumar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/monu_kumar"/>
    <language>en</language>
    <item>
      <title>The ₹1 That Costs ₹62,400: Modelling India's Tax Slabs Without Cliff-Edge Bugs</title>
      <dc:creator>Monu Kumar</dc:creator>
      <pubDate>Sat, 08 Aug 2026 17:24:28 +0000</pubDate>
      <link>https://dev.to/monu_kumar/the-1-that-costs-62400-modelling-indias-tax-slabs-without-cliff-edge-bugs-5fgm</link>
      <guid>https://dev.to/monu_kumar/the-1-that-costs-62400-modelling-indias-tax-slabs-without-cliff-edge-bugs-5fgm</guid>
      <description>&lt;p&gt;Progressive tax is the textbook example of a &lt;code&gt;reduce&lt;/code&gt;. Walk the slabs, tax each band at its rate, sum it up. Twelve lines, done.&lt;/p&gt;

&lt;p&gt;Then you hit a rebate threshold and discover your function has a cliff in it.&lt;/p&gt;

&lt;p&gt;In India's new tax regime, taxable income up to ₹12,00,000 attracts zero tax — not because the slabs produce zero, but because a rebate wipes out the computed liability. Run ₹12,00,000 through a naive implementation and you get ₹0. Run ₹12,00,001 and you get roughly ₹62,400.&lt;/p&gt;

&lt;p&gt;One extra rupee of income, ₹62,400 of extra tax. That is not how the law works, but it is exactly how an unpatched slab function behaves.&lt;/p&gt;

&lt;p&gt;I ran into this while building a regime-comparison tool, and the fix turned out to be the interesting part. This post covers the three discontinuities that break naive tax code, and a small binary search that answers a question closed-form algebra doesn't handle well.&lt;/p&gt;

&lt;p&gt;Code is TypeScript, framework-agnostic. I'll mention where React comes in at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually is a reduce
&lt;/h2&gt;

&lt;p&gt;Start with the easy bit. A slab is an upper bound and a rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Slab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NEW_REGIME_SLABS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Slab&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_200_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_600_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_400_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// null = no ceiling&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walking them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;slabTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Slab&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;upTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ceiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upTo&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountInBand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;amountInBand&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sanity check at ₹11,25,000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;band 1:        0 →   400,000  @ 0%   →      0
band 2:  400,000 →   800,000  @ 5%   → 20,000
band 3:  800,000 → 1,125,000  @ 10%  → 32,500
                                total = 52,500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's correct, and it's the last thing in this domain that behaves nicely.&lt;/p&gt;

&lt;p&gt;The single most common bug I've seen in hand-rolled tax code is taxing the &lt;em&gt;whole&lt;/em&gt; income at the top applicable rate instead of banding it. Someone earning ₹12,10,000 gets shown a 15% flat bill. The &lt;code&gt;floor&lt;/code&gt;/&lt;code&gt;ceiling&lt;/code&gt; walk above is what prevents that, and it's worth a test even though it looks obviously right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discontinuity one: the rebate cliff
&lt;/h2&gt;

&lt;p&gt;Section 87A grants a rebate that cancels tax entirely below a threshold. In the new regime for FY 2026-27 that's up to ₹60,000 of rebate for taxable income up to ₹12,00,000.&lt;/p&gt;

&lt;p&gt;Modelled naively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REBATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_200_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxRebate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;old&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxRebate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="nx"&gt;_500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyRebate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Regime&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRebate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;REBATE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// ← the cliff&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;maxRebate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plot &lt;code&gt;taxable → tax&lt;/code&gt; around the threshold and you get a step function. At ₹12,00,000 the slabs produce exactly ₹60,000, the rebate cancels it, output is zero. At ₹12,00,001 the rebate vanishes and the full ₹60,000 lands, plus cess.&lt;/p&gt;

&lt;p&gt;The law patches this with &lt;strong&gt;marginal relief&lt;/strong&gt;: tax is capped at the amount by which income exceeds the threshold. Earn ₹1 over, pay at most ₹1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyMarginalRelief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;taxAfterRebate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Regime&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;REBATE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;taxAfterRebate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;excessOverLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxAfterRebate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;excessOverLimit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the curve is continuous. From ₹12,00,000 upward, tax rises rupee-for-rupee with income until the relief stops binding — somewhere around ₹12,70,000 — after which normal slab tax takes over.&lt;/p&gt;

&lt;p&gt;That transition point is emergent, not hardcoded. It's wherever &lt;code&gt;slabTax - rebate&lt;/code&gt; crosses &lt;code&gt;income - limit&lt;/code&gt;. Worth understanding, because if you ever find yourself wanting to hardcode ₹12,70,000 as a magic number, you've misunderstood the mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discontinuity two: surcharge, same shape, bigger numbers
&lt;/h2&gt;

&lt;p&gt;High incomes attract a surcharge — a percentage &lt;em&gt;of the tax&lt;/em&gt;, not of income — that steps up at ₹50L, ₹1Cr, ₹2Cr and ₹5Cr.&lt;/p&gt;

&lt;p&gt;Same cliff, worse blast radius. At ₹50,00,000 there's no surcharge. At ₹50,00,001, a 10% surcharge applies to the entire tax liability. Without relief, one rupee of income triggers a tax jump in the lakhs.&lt;/p&gt;

&lt;p&gt;Surcharge marginal relief works the same way, phrased differently: the increase in &lt;em&gt;total tax&lt;/em&gt; cannot exceed the increase in &lt;em&gt;income&lt;/em&gt; over the threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SURCHARGE_BANDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.37&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;surchargeWithRelief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;baseTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Slab&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Regime&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;band&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;SURCHARGE_BANDS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawSurcharge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseTax&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Tax at the threshold itself, for comparison&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxAtThreshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyMarginalRelief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;applyRebate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;slabTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;regime&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incomeOverThreshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;band&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxAllowedTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taxAtThreshold&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;incomeOverThreshold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reliefCappedSurcharge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxAllowedTotal&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;baseTax&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawSurcharge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reliefCappedSurcharge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to note. The &lt;code&gt;.reverse().find()&lt;/code&gt; picks the highest applicable band — a plain &lt;code&gt;find&lt;/code&gt; on the ascending array returns 10% for a ₹3 crore income, which is wrong and won't show up unless you test at high incomes. And the new regime caps surcharge at 25% rather than 37%, so a production version needs a regime-aware band list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the pipeline together
&lt;/h2&gt;

&lt;p&gt;Order matters, and it's the order the statute specifies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaxInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TaxResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deductions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isSalaried&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ageGroup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;standardDeduction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isSalaried&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Chapter VI-A deductions apply to the old regime only&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applicableDeductions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;deductions&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taxable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;grossIncome&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;standardDeduction&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;applicableDeductions&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSlabs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ageGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gross&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;slabTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterReb&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyRebate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterRel&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyMarginalRelief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterReb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;surcharge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;surchargeWithRelief&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterRel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slabs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cess&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterRel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;surcharge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.04&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;taxable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterRel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;surcharge&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cess&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;monthlyTds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;afterRel&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;surcharge&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cess sits on top of tax &lt;em&gt;plus&lt;/em&gt; surcharge — a mistake that's easy to make and hard to spot, because it only skews results for high earners who aren't your test case while you're building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem: solving for break-even
&lt;/h2&gt;

&lt;p&gt;Here's the question the tool exists to answer, and the reason I wrote any of this.&lt;/p&gt;

&lt;p&gt;India lets you pick between two regimes. The new one has lower rates and almost no deductions. The old one has higher rates but allows deductions for investments, health insurance, rent and home loan interest.&lt;/p&gt;

&lt;p&gt;Every comparison tool tells you which regime is cheaper &lt;em&gt;given the deductions you typed in&lt;/em&gt;. Almost none answer the more useful question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much deduction would I need for the old regime to win at all?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's an inverse problem. Fix the income, treat old-regime tax as a function of deductions, and find where it crosses the constant new-regime tax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="nx"&gt;such&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nf"&gt;oldRegimeTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;income&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;newRegimeTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;income&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could solve it algebraically per slab, but the rebate, marginal relief and surcharge all introduce breakpoints, so a closed-form solution turns into a stack of case analysis that has to be rewritten every time a Budget moves a threshold.&lt;/p&gt;

&lt;p&gt;The function is monotonically decreasing in &lt;code&gt;D&lt;/code&gt; — more deductions never increase tax. Monotonic means binary search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findBreakEvenDeductions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deductions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oldAtZero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deductions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldAtZero&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;newTax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// old regime already wins&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oldAtMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deductions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldAtMax&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;newTax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// unreachable at any deduction level&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                    &lt;span class="c1"&gt;// old regime loses here&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// old regime wins here&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;midTax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deductions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;midTax&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;newTax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifty iterations narrows a ₹1 crore range to well under a rupee. Each iteration is a handful of arithmetic operations, so the whole solve runs in microseconds — fast enough to recompute on every keystroke without debouncing.&lt;/p&gt;

&lt;p&gt;The two guard clauses matter. &lt;code&gt;oldAtZero &amp;lt;= newTax&lt;/code&gt; catches incomes where the old regime already wins with nothing claimed. &lt;code&gt;oldAtMax &amp;gt; newTax&lt;/code&gt; catches the case where no deduction amount can close the gap, and returning &lt;code&gt;null&lt;/code&gt; there is honest — the alternative is a binary search converging on a meaningless boundary value that you then render as a real number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this number is worth computing
&lt;/h3&gt;

&lt;p&gt;Running it across incomes produces something more useful than any individual comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Income&lt;/th&gt;
&lt;th&gt;Break-even deductions needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;₹8L&lt;/td&gt;
&lt;td&gt;~₹2.5L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹10L&lt;/td&gt;
&lt;td&gt;~₹4.5L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹12L&lt;/td&gt;
&lt;td&gt;~₹6.5L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;₹20L&lt;/td&gt;
&lt;td&gt;~₹7.1L&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The realistic ceiling for someone without a home loan is roughly ₹3L — ₹1.5L of investments, ₹50k of pension contribution, up to ₹1L of health insurance. That's below the break-even at every row.&lt;/p&gt;

&lt;p&gt;Which means for a large share of users, the answer isn't "the new regime is cheaper today." It's "the old regime is structurally unreachable for you, and no amount of tax-saving products will change that." Same computation, considerably more decisive framing — and it only exists because the tool solves the inverse problem rather than the forward one.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://monumoney.in/calculators/old-vs-new-tax-regime-calculator" rel="noopener noreferrer"&gt;live version is here&lt;/a&gt; if you want to poke at the edge cases. Try ₹12,00,001 and ₹50,00,001 — those are the two cliffs from earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the cliffs
&lt;/h2&gt;

&lt;p&gt;Tax code is unusually well-suited to property-based testing, because the properties are legislated rather than invented.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tax never decreases as income increases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="nx"&gt;_919&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no cliff exceeds the income increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="nx"&gt;_919&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;grossIncome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;totalTax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second test is the one that earns its keep. It encodes the entire point of marginal relief — an extra rupee of income can never cost more than a rupee of tax — and it fails loudly on any threshold you've forgotten to patch. The odd step size (7,919) is deliberate; round increments have a habit of stepping neatly over the exact boundaries you're trying to catch.&lt;/p&gt;

&lt;p&gt;The rest is table-driven: known income/deduction pairs with hand-computed expected values, including one deliberately at each surcharge threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  React notes, briefly
&lt;/h2&gt;

&lt;p&gt;Nothing above touches the DOM, which is the point — it's a pure module with no dependencies, testable in isolation and portable.&lt;/p&gt;

&lt;p&gt;Two things worth mentioning about wiring it up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything runs client-side.&lt;/strong&gt; No income figures leave the browser, which sidesteps an entire category of privacy questions and lets the whole thing be statically served. Given the compute cost is microseconds, a server round-trip would be strictly worse in every dimension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js App Router splits cleanly.&lt;/strong&gt; The page is a Server Component that owns the metadata, the structured data and the long-form explanatory content — all statically rendered. Only the interactive panel is a Client Component. The explanatory content is what search engines index, and it costs zero client-side JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One &lt;code&gt;useMemo&lt;/code&gt;, not several.&lt;/strong&gt; Regime comparison plus break-even solve is a single derived computation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;newRegime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;oldRegime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computeTax&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;regime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;old&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;breakEven&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;findBreakEvenDeductions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Splitting these across multiple memos means multiple recomputation paths for the same underlying change, and the numbers can briefly disagree with each other mid-render. One input object, one memo, one consistent snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell past-me
&lt;/h2&gt;

&lt;p&gt;Three things, in order of how much time they cost me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model the discontinuities before writing the slab walk.&lt;/strong&gt; The slabs are the easy 20%. Rebates, marginal relief and surcharge are the 80%, and retrofitting them into a "finished" function means restructuring rather than extending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encode the law's invariants as tests, not the outputs.&lt;/strong&gt; "Tax is monotonic" and "no cliff exceeds its trigger" survive a Budget that moves every threshold. A test asserting &lt;code&gt;₹12,00,000 → ₹0&lt;/code&gt; breaks the moment a rate changes, which trains you to update tests reflexively instead of reading them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solve the inverse problem too.&lt;/strong&gt; Forward calculation — inputs to answer — is what everyone builds. The inverse — what would the input need to be for a different answer — is often the question the user actually has, and with a monotonic function it's twenty lines of binary search.&lt;/p&gt;

&lt;p&gt;If you're modelling a jurisdiction with similar rebate mechanics, I'd be curious how you handled it. The relief interactions are where I'd expect implementations to quietly diverge.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Slab rates and thresholds above reflect India's FY 2026-27 provisions as I understood them while building; verify against the &lt;a href="https://incometaxindia.gov.in" rel="noopener noreferrer"&gt;Income Tax Department&lt;/a&gt; before relying on any of it. This is a post about numerical edge cases, not tax advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Force AI to Write "Awwwards-Style" Minimalist UI in Next.js</title>
      <dc:creator>Monu Kumar</dc:creator>
      <pubDate>Wed, 22 Apr 2026 11:32:33 +0000</pubDate>
      <link>https://dev.to/monu_kumar/how-to-force-ai-to-write-awwwards-style-minimalist-ui-in-nextjs-3mfm</link>
      <guid>https://dev.to/monu_kumar/how-to-force-ai-to-write-awwwards-style-minimalist-ui-in-nextjs-3mfm</guid>
      <description>&lt;p&gt;Vibe coding is easy. Anyone can get ChatGPT to write a functional calculator in 20 minutes.&lt;/p&gt;

&lt;p&gt;Vibe coding a &lt;em&gt;beautiful&lt;/em&gt; calculator — one that looks like it belongs on Awwwards, loads instantly, feels premium in both dark and light mode, and doesn't make your users feel like they're using a 2019 Bootstrap template — that's the hard part nobody talks about.&lt;/p&gt;

&lt;p&gt;I've been building &lt;a href="https://monumoney.in" rel="noopener noreferrer"&gt;MonuMoney.in&lt;/a&gt; for the past three months. It's a fintech calculator platform — 14 calculators, full blog system, SEO infrastructure — all built with Next.js 15, Tailwind CSS, and Google AI Studio. No design agency. No senior dev. Just me, working 7 to 10 PM on weeknights after my 9-5 digital marketing job.&lt;/p&gt;

&lt;p&gt;The UI looks nothing like what AI usually generates. Here's how I forced it to stop giving me generic layouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem With AI-Generated UI
&lt;/h2&gt;

&lt;p&gt;When you give most devs (or AI) a blank prompt like "build me a calculator," you get this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;White background, black text&lt;/li&gt;
&lt;li&gt;Input fields with default browser styling&lt;/li&gt;
&lt;li&gt;A blue &lt;code&gt;#submit&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;&lt;code&gt;font-family: sans-serif&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Absolutely zero visual hierarchy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's functional. It's also forgettable.&lt;/p&gt;

&lt;p&gt;The issue isn't that AI can't write beautiful code. It's that you haven't constrained it enough. AI does what you ask. If you ask for "a calculator component," you get the average of everything it's ever seen — which is, statistically, pretty mediocre.&lt;/p&gt;

&lt;p&gt;You need prompt architecture, not just prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design System Lock-In Prompt
&lt;/h2&gt;

&lt;p&gt;Before I generate a single component, I establish a global design constraint. I give this to Claude first, which then writes production prompts for Google AI Studio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Design constraints for all components:
- Dark mode default, light mode toggle. Colors use CSS variables via Tailwind theme extension.
- bg-page (page background), bg-card (elevated surface), text-primary (headings), text-secondary (body), text-muted (captions)
- Border: border-border-subtle (thin, low contrast — never harsh)
- Accent colors: accent-blue, accent-green, accent-orange, accent-pink — used sparingly for data visualisation only, never for decorative borders
- Font stack: font-serif (Lora) for headings only, font-sans (Inter) for UI, font-mono (JetBrains Mono) for numbers
- Spacing: use pixel-precise values [24px], [32px], [48px], [64px] — not Tailwind utility shortcuts
- Border radius: [20px] for cards, [12px] for inputs, [8px] for buttons — consistent everywhere
- No box shadows. Depth comes from border + background contrast, not shadow.
- No gradients on text. Gradients allowed only on result cards as subtle background.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one block, prepended to every component prompt, eliminates 90% of generic AI output. The AI can't default to white backgrounds and blue buttons because those colours aren't in the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Typography Prompt That Creates Cinematic Feel
&lt;/h2&gt;

&lt;p&gt;Generic AI UI uses &lt;code&gt;text-xl&lt;/code&gt; and &lt;code&gt;text-2xl&lt;/code&gt; for everything. Premium UI uses intentional scale jumps with tight leading.&lt;/p&gt;

&lt;p&gt;Here's the exact typography instruction I include for hero numbers (like EMI results or SIP corpus values):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main result number: text-[40px] md:text-[48px] font-bold text-primary font-serif leading-none
Section H1: font-serif text-[32px] md:text-[48px] font-bold text-primary leading-[1.2] tracking-tight
Section H2: font-serif text-[24px] md:text-[32px] font-bold text-primary mb-[24px]
Body copy: text-[16px] leading-[1.7] text-secondary
Captions/meta: text-[13px] text-muted
Labels: text-[15px] font-medium text-primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;leading-none&lt;/code&gt; on the hero number is critical. It collapses line height to the font size, making large numbers feel sculptural rather than floaty. The &lt;code&gt;tracking-tight&lt;/code&gt; on headings prevents the loose letter-spacing that makes AI-generated headers look cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Card System That Builds Depth Without Shadows
&lt;/h2&gt;

&lt;p&gt;Every AI out of the box reaches for &lt;code&gt;shadow-lg&lt;/code&gt;. I banned shadows entirely in my design system. Here's the card pattern I enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Standard elevated card&lt;/span&gt;
&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-card border border-border-subtle rounded-[20px] p-[24px] md:p-[32px]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Result/highlight card (gradient instead of shadow)&lt;/span&gt;
&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-gradient-to-br from-card to-accent-green/5 border border-border-subtle rounded-[20px] p-[24px] md:p-[32px]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Table container&lt;/span&gt;
&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-card border border-border-subtle rounded-[20px] overflow-hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Table header row&lt;/span&gt;
&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-card-alt border-b border-border-subtle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;// (bg-card-alt is a slightly lighter/darker variant — creates depth purely through background contrast)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No shadows. The elevation hierarchy is: &lt;code&gt;bg-page&lt;/code&gt; → &lt;code&gt;bg-card&lt;/code&gt; → &lt;code&gt;bg-card-alt&lt;/code&gt;. Three levels. That's enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Prompt Architecture in Practice
&lt;/h2&gt;

&lt;p&gt;Here's what a real prompt for a calculator component looks like when you apply all these constraints. This is what I feed into Google AI Studio after Claude has pre-engineered it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Build&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmiCalculatorClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jsx&lt;/span&gt;

&lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Import&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;DESIGN&lt;/span&gt; &lt;span class="nc"&gt;SYSTEM &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Color&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;secondary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;muted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pink&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;hardcoded&lt;/span&gt; &lt;span class="nf"&gt;colors &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;fff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="o"&gt;-*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="o"&gt;-*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt; &lt;span class="nx"&gt;rounded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;br&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt; &lt;span class="nx"&gt;rounded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;shadows&lt;/span&gt; &lt;span class="nx"&gt;anywhere&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;always&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;pixel&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;arbitrary&lt;/span&gt; &lt;span class="nx"&gt;Tailwind&lt;/span&gt; &lt;span class="nx"&gt;like&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="nx"&gt;TYPOGRAPHY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Hero&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;bold&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;serif&lt;/span&gt; &lt;span class="nx"&gt;leading&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;medium&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Helper&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;muted&lt;/span&gt; &lt;span class="nx"&gt;mt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;LAYOUT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nx"&gt;gap&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nx"&gt;sticky&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;SLIDERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full h-[6px] bg-border-subtle rounded-lg appearance-none cursor-pointer accent-accent-blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="nx"&gt;alongside&lt;/span&gt; &lt;span class="nx"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt; &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt; &lt;span class="nx"&gt;rounded&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Then&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="nx"&gt;calculator&lt;/span&gt; &lt;span class="nx"&gt;logic&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The design constraints come first, before any business logic. AI prioritises what it sees first. Lead with design, and the logic fits around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mobile-First Card Grid Pattern
&lt;/h2&gt;

&lt;p&gt;The calculator index page uses a grid of cards. Here's the exact prompt snippet I use for any card grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Calculator cards grid layout:
- Container: grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-[16px]
- Each card: bg-card border border-border-subtle rounded-[20px] p-[20px] hover:border-primary/30 transition-colors block (as Link wrapper)
- Icon area: w-[48px] h-[48px] rounded-[16px] bg-page border border-border-subtle flex items-center justify-center mb-[16px]
- Card title: text-[15px] font-medium text-primary leading-[1.4] mb-[8px]
- Card description: text-[13px] text-secondary leading-[1.5]
- No decorative arrows or "→" icons — the entire card is clickable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;hover:border-primary/30&lt;/code&gt; instead of &lt;code&gt;hover:shadow-md&lt;/code&gt; is intentional. It creates a subtle focus state that stays within the design system. Adding shadow on hover is a Bootstrap habit. We don't do that here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dark Mode That Actually Works
&lt;/h2&gt;

&lt;p&gt;Most AI-generated dark modes are just &lt;code&gt;dark:bg-gray-900&lt;/code&gt;. That's not dark mode. That's just a grey background.&lt;/p&gt;

&lt;p&gt;My CSS variable system (in &lt;code&gt;globals.css&lt;/code&gt;) defines two sets of variables under &lt;code&gt;:root&lt;/code&gt; and &lt;code&gt;.dark&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafaf9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-card-alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1c1917&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#57534e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a8a29e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-border-subtle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e7e5e4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0f0f0e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1917&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-card-alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#242320&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafaf9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a8a29e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#57534e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-border-subtle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2d2b28&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in &lt;code&gt;tailwind.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-page)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-card)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card-alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-card-alt)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-secondary)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-muted)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-subtle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-border-subtle)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when I tell AI to use &lt;code&gt;bg-card&lt;/code&gt; and &lt;code&gt;text-primary&lt;/code&gt;, it works correctly in both modes automatically. No &lt;code&gt;dark:&lt;/code&gt; prefixes scattered throughout the JSX. The component is mode-agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://monumoney.in" rel="noopener noreferrer"&gt;MonuMoney.in&lt;/a&gt; is the live, production demo of this entire framework — 14 fintech calculators, full dark/light mode, mobile responsive, built entirely through AI prompts following the architecture above.&lt;/p&gt;

&lt;p&gt;The UI has been called "looks like a real product" by multiple people who assumed it was professionally designed. It wasn't. It was prompt engineering.&lt;/p&gt;

&lt;p&gt;The difference between "AI-generated crud" and "AI-generated product" is constraint specificity. The more precisely you define your design system in the prompt, the less the AI has to guess — and the better the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference: The Anti-Generic Checklist
&lt;/h2&gt;

&lt;p&gt;Before you submit any UI prompt to AI, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All colors are design tokens, not hex values or Tailwind grey utilities&lt;/li&gt;
&lt;li&gt;[ ] No box shadows — depth from background contrast only&lt;/li&gt;
&lt;li&gt;[ ] Typography scale specified explicitly with pixel values&lt;/li&gt;
&lt;li&gt;[ ] Card pattern specified once, applied everywhere&lt;/li&gt;
&lt;li&gt;[ ] Mobile layout specified (stacked on small, grid on lg)&lt;/li&gt;
&lt;li&gt;[ ] Dark mode handled via CSS variables, not &lt;code&gt;dark:&lt;/code&gt; prefixes&lt;/li&gt;
&lt;li&gt;[ ] No "click here" buttons — every interactive element has purposeful label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole system. Not magic. Just constraint.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building something with this approach? Drop a comment — I'm curious what you're making.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>vibecoding</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>From ₹15,000 in Debt to Building a Fintech Side Project: My "Vibe Coding" Journey</title>
      <dc:creator>Monu Kumar</dc:creator>
      <pubDate>Sun, 12 Apr 2026 07:20:53 +0000</pubDate>
      <link>https://dev.to/monu_kumar/from-15000-in-debt-to-building-a-fintech-side-project-my-vibe-coding-journey-dhp</link>
      <guid>https://dev.to/monu_kumar/from-15000-in-debt-to-building-a-fintech-side-project-my-vibe-coding-journey-dhp</guid>
      <description>&lt;p&gt;It's funny how you can have a B.Tech in Computer Science, understand complex data structures, &lt;br&gt;
and write production-level code, but completely fail at basic personal finance.&lt;/p&gt;

&lt;p&gt;A few years ago, at 22, I landed my first real full-time job. I was managing digital marketing, &lt;br&gt;
technical SEO, and Meta ads. Coming from a middle-class family in Panipat, Haryana, seeing a &lt;br&gt;
regular salary hit my bank account gave me a false sense of invincibility.&lt;/p&gt;

&lt;p&gt;I fell into the classic entry-level tech trap: lifestyle creep.&lt;/p&gt;

&lt;p&gt;I started swiping my new credit card for weekend dinners at Hangries. I bought a premium Fossil &lt;br&gt;
watch just to "look the part" in client meetings. I bought SaaS subscriptions I didn't need.&lt;/p&gt;

&lt;p&gt;Before I knew it, I was sitting on ₹15,000 in credit card debt. I had zero savings, and the &lt;br&gt;
anxiety of living paycheck-to-paycheck was destroying my productivity.&lt;/p&gt;

&lt;p&gt;Here is the story of how I coded my way out of it, why I pivoted to "vibe coding," and how my &lt;br&gt;
financial mistakes led me to build my own fintech side project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point: Engineering a Way Out
&lt;/h2&gt;

&lt;p&gt;As developers, when we see a bug in our code, we debug it. I realized I needed to debug my &lt;br&gt;
finances.&lt;/p&gt;

&lt;p&gt;I stopped swiping. I listed out every single rupee I owed. I decided to leverage my existing &lt;br&gt;
9-to-5 skills — SEO, coding, and digital marketing — to build local side hustles. I started &lt;br&gt;
walking into local shops in my city, offering to set up their Google My Business profiles and &lt;br&gt;
build them micro-tools using simple HTML/JS.&lt;/p&gt;

&lt;p&gt;I used the extra income to attack my debt using the Snowball Method — paying off the smallest &lt;br&gt;
balances first to build psychological momentum.&lt;/p&gt;

&lt;p&gt;I actually wrote the exact mathematical breakdown of &lt;a href="https://monumoney.in/blog/debt-payoff-snowball-method-india" rel="noopener noreferrer"&gt;how I cleared my debt using the snowball method&lt;/a&gt;, which inspired me to build the &lt;a href="https://monumoney.in/calculators/debt-payoff-calculator" rel="noopener noreferrer"&gt;Debt Payoff Calculator&lt;/a&gt; &lt;br&gt;
for my new project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Over-Engineering" Trap
&lt;/h2&gt;

&lt;p&gt;Once I was debt-free, I wanted to share my frameworks with other young professionals in India &lt;br&gt;
so they wouldn't make the same dumb mistakes I did. I decided to build a personal finance hub: &lt;br&gt;
MonuMoney.in.&lt;/p&gt;

&lt;p&gt;But like any developer, my first instinct was to over-engineer it.&lt;/p&gt;

&lt;p&gt;I started planning a massive Next.js architecture with a heavy backend, user authentication, a &lt;br&gt;
PostgreSQL database for saving calculator states, and complex global state management.&lt;/p&gt;

&lt;p&gt;Two weeks passed. I hadn't shipped a single thing. I was stuck in "tutorial hell" and &lt;br&gt;
architecture paralysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embracing "Vibe Coding" with AI
&lt;/h2&gt;

&lt;p&gt;That's when I decided to completely change my approach. I had been hearing about "vibe coding" — &lt;br&gt;
the process of using AI tools like Claude and Google AI Studio to write the boilerplate, while &lt;br&gt;
you focus purely on the logic, architecture, and "vibe" of the product.&lt;/p&gt;

&lt;p&gt;I dropped the complex backend. I realized users don't want to log in to calculate their debt; &lt;br&gt;
they just want a fast, client-side web app.&lt;/p&gt;

&lt;p&gt;Here was my new stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Next.js (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Tailwind CSS (for rapid UI prototyping)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Vercel (push to ship)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Secret Weapon:&lt;/strong&gt; Prompting AI for component generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing a complex amortization formula from scratch in JavaScript, I prompted the AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Write a React component using Tailwind that calculates the debt snowball method. It needs &lt;br&gt;
to take in an array of debts (name, balance, interest rate, minimum payment) and output a &lt;br&gt;
month-by-month payoff table."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within seconds, I had the core logic. I spent my time doing what human developers do best: &lt;br&gt;
refining the UI, fixing edge-case bugs, adjusting the layout for mobile screens, and ensuring &lt;br&gt;
the local context — like using ₹ instead of $ — was perfect.&lt;/p&gt;

&lt;p&gt;By vibe coding, I built and deployed three complex financial calculators in a single weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway for Indie Hackers and Devs
&lt;/h2&gt;

&lt;p&gt;Building MonuMoney has taught me a few massive lessons that I want to pass on to the Dev.to &lt;br&gt;
community.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Solve Your Own Bugs (In Real Life)
&lt;/h3&gt;

&lt;p&gt;The best side projects come from personal pain. I built a debt payoff calculator because I &lt;br&gt;
literally needed one when I was ₹15k in the hole. If you build a tool that solves your own &lt;br&gt;
problem, you automatically have a target audience of at least one person.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don't Over-Engineer Version 1.0
&lt;/h3&gt;

&lt;p&gt;Nobody cares about your database schema if the product doesn't exist yet. Vibe code the MVP. &lt;br&gt;
Use AI to write the boring boilerplate. Ship it as a static client-side app first. You can &lt;br&gt;
always add a database later when people actually start using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Your Day Job is Your Superpower
&lt;/h3&gt;

&lt;p&gt;Don't try to build the next Facebook on weekends. Look at what you do from 9-to-5 and figure &lt;br&gt;
out how to spin that into a localized micro-SaaS or a side hustle.&lt;/p&gt;

&lt;p&gt;When I recently &lt;a href="https://monumoney.in/blog/ai-proof-side-hustles-india-2026" rel="noopener noreferrer"&gt;tested 5 different side hustles to see what actually works&lt;/a&gt;, &lt;br&gt;
the highest-earning ones were directly tied to my existing professional skills in SEO and &lt;br&gt;
digital marketing — not some random trend I found on YouTube.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where MonuMoney.in Stands Today
&lt;/h2&gt;

&lt;p&gt;Today, my credit card balance is zero. I have multiple income streams running simultaneously. &lt;br&gt;
And I'm building out MonuMoney.in in public — sharing every real number, every failed &lt;br&gt;
experiment, and every win.&lt;/p&gt;

&lt;p&gt;The tools I've shipped so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Debt Payoff Calculator&lt;/strong&gt; (Snowball + Avalanche methods)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Side Hustle Income Tracker&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Monthly Budget Planner&lt;/strong&gt; built for the Indian salary structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is client-side. Everything is fast. Everything is built for the Indian rupee and &lt;br&gt;
Indian financial context — because most personal finance tools online are built for Americans &lt;br&gt;
and don't translate well here.&lt;/p&gt;

&lt;p&gt;If you're a dev sitting on an idea — stop architecting it in your head. Spin up a Next.js app, &lt;br&gt;
use AI to vibe code the boring parts, and just hit deploy.&lt;/p&gt;

&lt;p&gt;The best time to ship was last week. The second best time is right now.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>career</category>
    </item>
    <item>
      <title>The Gumroad Illusion: Why I Stopped Selling Digital Products and Went Back to Service Delivery</title>
      <dc:creator>Monu Kumar</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:30:48 +0000</pubDate>
      <link>https://dev.to/monu_kumar/the-gumroad-illusion-why-i-stopped-selling-digital-products-and-went-back-to-service-delivery-d7a</link>
      <guid>https://dev.to/monu_kumar/the-gumroad-illusion-why-i-stopped-selling-digital-products-and-went-back-to-service-delivery-d7a</guid>
      <description>&lt;p&gt;Let me guess—you’ve seen the tweets. &lt;/p&gt;

&lt;p&gt;"Build a Notion template in 2 hours and make ₹5 lakhs while you sleep." &lt;/p&gt;

&lt;p&gt;"Create a PDF checklist, put it on Gumroad, and wake up to passive income."&lt;/p&gt;

&lt;p&gt;As a digital marketer with a B.Tech in Computer Science, I bought into this dream completely. In January 2026, I decided I was going to launch my first digital product. I was going to join the "passive income" club. &lt;/p&gt;

&lt;p&gt;I built it, launched it, shared it, and waited for the money to roll in.&lt;/p&gt;

&lt;p&gt;It didn’t. &lt;/p&gt;

&lt;p&gt;Today, I’m going to share the exact math of my digital product failure, why I immediately pivoted back to service delivery, and the hard lesson every marketer needs to hear about building products before building an audience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dream: "Build Once, Sell Forever"
&lt;/h2&gt;

&lt;p&gt;The idea seemed bulletproof. My day job involves managing SEO, Google Ads, and local search campaigns. I know exactly how to rank a local business on Google Maps. &lt;/p&gt;

&lt;p&gt;So, I packaged my knowledge into a "Local Business SEO Checklist + Template Bundle." It included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A step-by-step Google My Business optimization guide.&lt;/li&gt;
&lt;li&gt;  A WhatsApp marketing template for local shops.&lt;/li&gt;
&lt;li&gt;  A Google review request script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I priced it at ₹199. A no-brainer, right? Anyone running a small business should want this.&lt;/p&gt;

&lt;p&gt;I listed it on Gumroad. I posted the link on LinkedIn and Twitter (X). I sat back and waited for the notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math of My Failure
&lt;/h2&gt;

&lt;p&gt;Here is the unvarnished truth of what happened over 30 days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Copies sold:&lt;/strong&gt; 2&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Gross Revenue:&lt;/strong&gt; ₹398&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Gumroad Fees:&lt;/strong&gt; ~₹58&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Net Profit:&lt;/strong&gt; ₹340&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two copies. And to make matters worse, both buyers were &lt;em&gt;other digital marketers&lt;/em&gt; researching what I was doing—not the local business owners I had created the product for.&lt;/p&gt;

&lt;p&gt;This wasn't just a failed product launch; it was a reality check. When I recently &lt;a href="https://monumoney.in/blog/ai-proof-side-hustles-india-2026" rel="noopener noreferrer"&gt;tested 5 different side hustles to see what actually works&lt;/a&gt;, my digital product was the only one that failed spectacularly, earning just ₹340. Meanwhile, offering the exact same skills as a localized service earned me ₹7,500 in the same month. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Digital Products Fail for Beginners (The 3 Hard Truths)
&lt;/h2&gt;

&lt;p&gt;I spent a lot of time analyzing why this happened. The product was good. The price was accessible. The problem was fundamental. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Distribution &amp;gt; Product
&lt;/h3&gt;

&lt;p&gt;We live in an era of information abundance. A local business owner doesn't need my ₹199 PDF—they can ask ChatGPT for a GMB optimization guide for free. What they &lt;em&gt;don't&lt;/em&gt; have is the time to read it or the technical know-how to execute it. &lt;/p&gt;

&lt;p&gt;I had zero audience. No newsletter. No SEO traffic. No community. A product listing on Gumroad without a distribution channel is like opening a shop in the middle of the Sahara. It doesn't matter how good your product is; nobody is walking by.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The "₹199 Dead Zone"
&lt;/h3&gt;

&lt;p&gt;I learned that ₹199 is a terrible price point for strangers. It’s too expensive to be an impulse buy, but too cheap to signal high value. If I had given it away for free in exchange for email addresses, it would have built my audience. If I had priced it at ₹999 and positioned it as a premium "Done-With-You" framework, the two buyers might have still purchased it, and I would have made ₹2,000 instead of ₹400. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. I Sold Information, Not Transformation
&lt;/h3&gt;

&lt;p&gt;Digital products are passive. They deliver information. But businesses don't pay for information; they pay for transformation. When I walked into a local medical store and offered to set up their Google My Business profile, I wasn't selling a PDF. I was selling them the outcome of getting more local foot traffic. That human connection, the handholding, the accountability—that’s what they paid me ₹1,500 for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot: Why Service Delivery Won
&lt;/h2&gt;

&lt;p&gt;After the digital product flop, I doubled down on what actually worked: delivering the service myself. &lt;/p&gt;

&lt;p&gt;I walked into 11 local businesses in my city. I showed them their own Google listings on my phone. I explained, in simple Hindi, how their competitors were stealing clicks from Google Maps. &lt;/p&gt;

&lt;p&gt;My pitch was simple: &lt;em&gt;"I will set this up completely. If you get even one new customer in 30 days, pay me ₹1,500. If not, it is free."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Eight said yes. Six saw measurable results. Five paid me. One paid me ₹2,500 and asked for monthly management. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total earned: ₹7,500. Time invested: 15 hours.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare that to the ₹340 from the PDF. The ROI wasn't even close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Way to Build Digital Products
&lt;/h2&gt;

&lt;p&gt;I haven't given up on digital products. Passive income is real, but the sequence matters. The "gurus" get the order wrong. They say: &lt;em&gt;Build Product → Launch → Get Audience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The correct order is: &lt;strong&gt;Build Audience → Identify Pain Points → Sell Service → Productize the Service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is my revised playbook for 2026:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Sell your skills as a service first.&lt;/strong&gt; Prove people will pay to solve the problem.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Document the process.&lt;/strong&gt; Turn your service delivery into content (blogs, LinkedIn posts, videos).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Build the audience.&lt;/strong&gt; Attract people who want the result but can't afford your 1-on-1 service.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Launch the product.&lt;/strong&gt; Now you have a distribution channel and a proven market.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;If you are a marketer, developer, or designer sitting on a pile of skills, do not start by building a digital product in a vacuum. You are competing against free AI tools that can generate 90% of informational PDFs in 30 seconds. &lt;/p&gt;

&lt;p&gt;Instead, sell your trust. Sell your presence. Sell your ability to look a business owner in the eye and say, "I will handle this for you." &lt;/p&gt;

&lt;p&gt;Service delivery isn't passive. It requires sweat, phone calls, and follow-ups. But in an AI-driven world where anyone can generate a checklist, human trust is the most valuable currency you have. &lt;/p&gt;

&lt;p&gt;Build the trust first. The passive income will follow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sidehustles</category>
      <category>digitalproducts</category>
      <category>career</category>
    </item>
    <item>
      <title>The Reality of Vibe Coding: How I Built a Web App Without "Writing" Code</title>
      <dc:creator>Monu Kumar</dc:creator>
      <pubDate>Sat, 28 Mar 2026 06:13:21 +0000</pubDate>
      <link>https://dev.to/monu_kumar/the-reality-of-vibe-coding-how-i-built-a-web-app-without-writing-code-911</link>
      <guid>https://dev.to/monu_kumar/the-reality-of-vibe-coding-how-i-built-a-web-app-without-writing-code-911</guid>
      <description>&lt;p&gt;I have a confession. I have a B.Tech in Computer Science. I understand programming concepts. I can read code and mostly understand what it does.&lt;/p&gt;

&lt;p&gt;But I did not write the code for my website in the traditional sense. I built the entire thing using Google AI Studio. Prompt by prompt. Component by component.&lt;/p&gt;

&lt;p&gt;And it works. It is live. Real people use it.&lt;/p&gt;

&lt;p&gt;Let me tell you how.&lt;/p&gt;

&lt;p&gt;What I Built&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://monumoney.in/calculators" rel="noopener noreferrer"&gt;personal finance calculator platform&lt;/a&gt; for Indians called MonuMoney.in. It has 36 financial calculators (8 fully functional right now, 28 being built), a blog, SEO setup, and the whole thing runs on Next.js 15 with Tailwind CSS deployed on Vercel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rr6i52twl6sr7ik6s6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rr6i52twl6sr7ik6s6q.png" alt=" " width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The calculators do real-time EMI calculations, SIP return projections, income tax computation based on Indian tax slabs, retirement corpus planning, and more.&lt;/p&gt;

&lt;p&gt;This is not a toy project. The calculations need to be accurate because people will make real financial decisions based on the numbers.&lt;/p&gt;

&lt;p&gt;What Vibe Coding Actually Means&lt;/p&gt;

&lt;p&gt;There is a lot of hype around "vibe coding" right now, and most of it misses the point. Let me explain what it actually looks like in practice.&lt;/p&gt;

&lt;p&gt;Vibe coding is not "tell AI to build me an app." That does not work for anything beyond a demo.&lt;/p&gt;

&lt;p&gt;Vibe coding is more like pair programming where your AI partner does the typing and you do the thinking. You need to know what you want. You need to understand the architecture. You need to be able to read the output and know if it is right or wrong.&lt;/p&gt;

&lt;p&gt;Here is my actual workflow:&lt;/p&gt;

&lt;p&gt;Step 1: I decide what I need. For example, "I need an EMI calculator page with SEO metadata, structured data, and a client-side interactive component."&lt;/p&gt;

&lt;p&gt;Step 2: I write a detailed prompt that specifies exactly what I want. Not "build me an EMI calculator" but a prompt that describes the inputs, the formula, the output display, the styling classes, the file structure, the metadata format, everything.&lt;/p&gt;

&lt;p&gt;Step 3: Google AI Studio generates the code. I read through it. Sometimes it is 90% right and I ask it to fix the remaining issues. Sometimes it completely misunderstands what I wanted and I rephrase.&lt;/p&gt;

&lt;p&gt;Step 4: I paste the final code into my project, test it locally, and push to GitHub. Vercel deploys it automatically.&lt;/p&gt;

&lt;p&gt;This cycle takes anywhere from 20 minutes for a simple component to 3 or 4 hours for a complex calculator page.&lt;/p&gt;

&lt;p&gt;Why Google AI Studio and Not Cursor or Copilot&lt;/p&gt;

&lt;p&gt;No particular reason honestly. I started with Google AI Studio because it was free and I was already comfortable with the Google ecosystem. It just stuck.&lt;/p&gt;

&lt;p&gt;Could I do the same thing with Cursor, Copilot, or Claude? Probably yes. The tool matters less than the skill of writing clear, specific prompts.&lt;/p&gt;

&lt;p&gt;Things That Work Well With This Approach&lt;/p&gt;

&lt;p&gt;Building UI components is where vibe coding really shines. Tailwind CSS components, page layouts, responsive grids, card designs. These are things where the AI has seen millions of examples and can generate solid output consistently.&lt;/p&gt;

&lt;p&gt;SEO boilerplate is another winner. Metadata objects, structured data JSON-LD, sitemap generation, robots.txt configuration. This stuff is repetitive and well-documented. AI handles it perfectly every time.&lt;/p&gt;

&lt;p&gt;Standard financial formulas also work great. EMI formula, compound interest, SIP returns. These formulas are well known and the AI implements them correctly.&lt;/p&gt;

&lt;p&gt;Things That Do Not Work Well&lt;/p&gt;

&lt;p&gt;Anything involving complex state management across multiple components gets messy. The AI generates code that works in isolation but does not always integrate cleanly with existing components. You end up spending time debugging state conflicts.&lt;/p&gt;

&lt;p&gt;Design consistency is another challenge. If you generate each component in a separate prompt, they might use slightly different spacing, colors, or patterns. I learned to always include my existing design tokens and class names in every prompt.&lt;/p&gt;

&lt;p&gt;Edge cases in financial calculations are the scariest part. The AI might implement the basic EMI formula correctly but miss edge cases like what happens when the interest rate is zero, or when the tenure is less than one month. You absolutely need to test every calculator with extreme values.&lt;/p&gt;

&lt;p&gt;The Skill That Actually Matters&lt;/p&gt;

&lt;p&gt;The most important skill in vibe coding is not prompt engineering. It is knowing what to ask for.&lt;/p&gt;

&lt;p&gt;If you do not understand how a Next.js App Router works, you cannot write a good prompt for it. If you do not know what structured data is, you cannot ask the AI to implement it. If you do not understand the EMI formula, you cannot verify the AI's output.&lt;/p&gt;

&lt;p&gt;My B.Tech CS background helps a lot here. Not because I write code, but because I can read it. I understand file structures, component hierarchies, client vs server rendering, and basic algorithmic logic. When the AI generates something wrong, I can usually tell and explain what needs to change.&lt;/p&gt;

&lt;p&gt;Vibe coding does not eliminate the need for technical knowledge. It eliminates the need for typing speed and syntax memorization. Those are very different things.&lt;/p&gt;

&lt;p&gt;The Financial Accuracy Problem&lt;/p&gt;

&lt;p&gt;This deserves its own section because it is the most important consideration for a project like this.&lt;/p&gt;

&lt;p&gt;When you build a calculator that people use for financial decisions, accuracy is not optional. If my EMI calculator shows a wrong number and someone budgets based on it, that is a real problem.&lt;/p&gt;

&lt;p&gt;My approach:&lt;/p&gt;

&lt;p&gt;Every formula is cross-verified against at least two established sources. I check against known correct values from bank websites or government portals.&lt;/p&gt;

&lt;p&gt;I test with known inputs and expected outputs. For example, I know that a Rs 10 lakh loan at 8% for 10 years should give an EMI of Rs 12,133. If my calculator shows anything different, something is wrong.&lt;/p&gt;

&lt;p&gt;I always include a disclaimer that calculations are for educational and estimation purposes only. Not because I am not confident in the accuracy, but because financial tools should always carry this disclaimer.&lt;/p&gt;

&lt;p&gt;Would I Recommend This Approach&lt;/p&gt;

&lt;p&gt;For building tool-based websites and utility apps, absolutely yes. Vibe coding with an AI assistant is faster than traditional coding for someone at my skill level. Probably 3 to 4 times faster.&lt;/p&gt;

&lt;p&gt;For building complex applications with lots of interconnected state, authentication, databases, and real-time features, I would be more careful. At some point, the complexity of prompts exceeds the complexity of just writing the code yourself.&lt;/p&gt;

&lt;p&gt;For my use case, a collection of independent calculators on a static site, vibe coding is perfect. Each calculator is self-contained. Each page follows the same pattern. The architecture is simple and repetitive. This is exactly where AI-assisted coding performs best.&lt;/p&gt;

&lt;p&gt;The site is live at &lt;a href="https://monumoney.in" rel="noopener noreferrer"&gt;https://monumoney.in&lt;/a&gt; if you want to see the end result.&lt;/p&gt;

&lt;p&gt;If you have questions about the workflow or specific prompts I used, drop them in the comments. Happy to share more details.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>personalfinance</category>
      <category>vibecoding</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
