D
P
0
← All articles Baca dalam Bahasa Indonesia

CSS Layout, Overflow & Cascade

Every `rem` Shrank 19% After `html { font-size: 13px }`? `10rem` Became 130px Because `rem` Follows the Root

· · 7 min read
Every `rem` Shrank 19% After `html { font-size: 13px }`? `10rem` Became 130px Because `rem` Follows the Root

The spec was a single sentence: the product UI is 13px with line-height 1.5. It arrived on 7 September 2026 in a brand note from the client side, forwarded by their backend developer, for the client web app I was working on.

I translated it in the most literal way possible, as html { font-size: 13px }, and according to my notes that line went into the app's code the same day. I did not catch it myself. An automated review flagged it before the push. At a glance the line looks harmless, because the number is exactly what was asked for. The trouble is that rem resolves against the root element, so the same line also re-bases every rem in the codebase.

rem reads the root, not the text

My notes sum up the mistake in one sentence: the 13px spec is a rule about text, not a rule about the root. Before the change, the app had no root font-size at all, so 1rem was the browser default of 16px. The html rule in its global style file, src/styles/base.ts, held only a font-family, box-sizing and line-height: 1. Once the root got 13px, 1rem became 13px as well. The arithmetic: 13 divided by 16 is 0.8125. Every rem length keeps 81.25 percent of its previous size, a shrink of 18.75 percent that rounds to 19.

The shrink landed on 39 rem declarations across 12 files, and none of them looked connected to the edit. Three of them are enough to see the pattern:

file (before the fix)           declaration           1rem = 16px   1rem = 13px
index.tsx, line 1556            height: '10rem'       160px         130px
styledComponents.ts, line 86    border-radius: 1rem   16px          13px
styledComponents.ts, line 98    border-radius: 1rem   16px          13px

The height is an inline style in the JSX of a component file, and it quietly became 130px instead of 160px. The two border-radius: 1rem lines in styledComponents.ts each became 13px. CSS raises no error for a change like this. The box is simply 30px shorter and the corners a little less rounded than before, far away from the line that was edited.

A global reset that passes the root along

My notes flag one more factor that made things worse. The same global style file has a reset that passes five properties down to every element at once:

*, *::before, *::after {
  box-sizing: inherit;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

font-size is already inherited by default, so this reset does not create inheritance. What it removes are the exceptions. The browser's default stylesheet gives headings and form controls their own sizes, and font-size: inherit on every element overrides them. As a result, whatever the root carries spreads into every element that does not set its own size. With 13px on html, that single value became both the base for 39 rem lengths and the text size of every element without a size of its own.

The screen pack already showed the shape

The answer was already available before that line was written. The design screen pack that came with this work showed the right shape, and I did not read it carefully enough. Its stylesheet has no html rule at all. Its body rule uses 16px with line-height 1.6, and the 13px and 1.5 pair only appears on the .app container:

/* screen pack stylesheet, simplified to the relevant properties */
/* there is no html rule at all */
 
body {
  font-size: 16px;
  line-height: 1.6;
}
 
.app {
  font-size: 13px;
  line-height: 1.5;
}

That is exactly the way to size product text without touching rem. The root stays at the browser default, so 1rem stays 16px, while 13px applies only inside the app container.

Take the font size off html and move it to body

The rule for the fix is simple: put the product size on body or on the app container, and leave html without a font-size. In this app the choice was body. The fix commit on 7 September put font-size: 13px and line-height: 1.5 on the body rule. The same commit swapped the font-family on html, dropped line-height: 1 from it, and set the fontSize token in the Ant Design theme, the component library this app uses, to 13 so its components follow the same text size.

The commit message spells out the reason: the pair sits on body rather than html on purpose, because putting it on html would re-base every rem from 16px to 13px and quietly shrink the 39 rem lengths. The whole commit touches only five files, public/index.html and four files under src/styles. The two component files holding the examples above are not among them. The fix works from the root side rather than from the side of whatever uses rem.

The commit reached the remote branch in a push that same day. The local git history holds no commit that puts font-size: 13px on html. The only commit that added that string to the global style file is the fix commit, and there the value sits on body.

As of 1 October nothing had changed. The html rule still has no font-size, and body still carries 13px/1.5 along with a comment explaining why:

/* src/styles/base.ts as of 1 October 2026, trimmed to the relevant part */
html {
  box-sizing: border-box;
}
 
/*
 * Product text is 13px with line-height 1.5, and it sits on body rather than
 * html on purpose. The pack does the same: it has no html rule at all and
 * scopes the pair to its .app container. Putting it on html would re-base
 * every rem in the app from 16px to 13px, which silently shrinks the 39 rem
 * lengths spread across the codebase.
 */
body {
  font-size: 13px;
  line-height: 1.5;
}

To me that comment matters as much as the fix itself. To anyone who does not know the story, moving 13px from body to html would look like a sensible tidy-up.

Measure in the browser, do not trust the CSS

My notes require one more step: verify in the browser by measuring a probe element rather than trusting the CSS. The probe is an absolutely positioned div off screen, 1rem wide and 10rem tall:

const p = document.createElement('div')
p.style.cssText = 'position:absolute;left:-9999px;width:1rem;height:10rem'
document.body.appendChild(p)
p.getBoundingClientRect()   // expect 16 and 160, not 13 and 130

The right answer is 16 and 160, not 13 and 130. The 10rem height makes the difference easy to read, 30px rather than just 3px. Because rem only reads the root, a probe appended to body still measures 16 and 160 even though body carries 13px. Reading the CSS is not enough, since the value of 1rem comes from the computed font-size of the root, and any stylesheet the page loads can set that. The probe measures the end result.

The same probe lives in two measurement scripts I use for theme work on this app. One stores its result as remProbe, and the other carries the comment // rem probe: must stay 16/160.

I should be straight about the order of the evidence. The note from the day itself only says the mistake was caught before the push, then describes how to apply and measure the fix. It does not record a probe result for 7 September. The earliest stored measurement is from 21 September, when the census taken before the global theme work saved "remProbe": [16, 160]. A browser measurement the next day read 16/160 with zero page errors, the probe stayed at 16/160 before and after that theme change, and a theme measurement on 1 October again recorded 16/160 with zero page errors. The global theme notes themselves set a rule never to put a font size on html, pointing back to the note about this bug.

A text spec is not an instruction for the root

What makes this mistake slippery is that the number was right. The 13px was exactly what had been asked for, and only the place was wrong. A spec like "the product UI is 13px with line-height 1.5" talks about text, while a font-size on html is a decision about the unit the whole codebase measures with. The two just happen to be written with the same CSS property.

So when you receive a text size spec, put it on body or on the app container and leave html without a font-size. If the spec comes with a design file, read that file's stylesheet and look at which element carries the number. Check as well whether a reset passes font-size down to every element, because a reset like that lets anything placed on the root spread further. Finally, measure in the browser with one div that is 1rem wide and 10rem tall, which has to read 16 and 160.