Blog
Two measurements the browser gets wrong by default
Screen height on mobile and Thai type next to Latin are both fine in a mockup and wrong on a real device.
Published
You open a link on your phone. The first screen is meant to be a full-height panel, but the button at the bottom is cut in half. You scroll a little to reach it, the browser toolbars slide away, and the whole page shifts — the line you were reading moves out from under your eyes.
Two different default behaviours, both of which look entirely correct until you hold a real device.
The screen height that is not the height you can see
100vh does not mean the height of the area currently visible. The specification defines vh against the largest possible viewport: the state where the address bar and the bottom toolbar have retracted completely.
On a desktop this never matters, because the browser chrome does not move. On a phone it matters immediately. When someone first opens the page, both bars are showing, so the visible area is several dozen pixels shorter than 100vh. A hero set to 100vh is therefore clipped at the bottom in the first second anyone sees the site — which is the second that counts most.
There are now units that say what you mean.
lvh is that largest viewport, stated explicitly rather than by accident.
svh is the smallest viewport — the state with the toolbars present.
dvh is dynamic: it grows as the toolbars retract and shrinks as they return.
Why this site chose svh
The hero on this site's home page is min-h-[calc(100svh-6rem)], not dvh, even though dvh looks like the more accurate answer.
The reason is the word dynamic. With dvh the panel fits the screen perfectly at every instant, but as the reader scrolls and the toolbars retract, the panel grows — pushing the content below it downward at the same moment the reader is pulling it upward. Text slides out from under the eye exactly when someone is trying to read it.
svh takes the other side of the trade. It pins the panel to the smallest viewport, so the whole thing is visible at rest without scrolling, and it never resizes again regardless of what the toolbars do. The price is a little empty space at the bottom once the bars have gone.
That is a good trade, and it generalises well beyond viewport units: prefer a layout that is stable over one that is momentarily optimal. Thirty spare pixels go unnoticed. A page that moves under the reader is felt every time.
Thai that comes out smaller without anyone asking
The second measurement is in this very paragraph.
This site sets Latin in Poppins and Thai in Noto Sans Thai. Both are given exactly the same font-size. On screen, the Thai is visibly smaller.
font-size does not set the size of the letters you see. It sets the size of the em — an abstract box in which each type designer decides independently how large to draw the letters. The proportion that matters to a reader is the x-height relative to that em, and the two families do not agree on it. People judge size by x-height, not by the em box.
Height is the other half of the problem. Thai stacks vowels and tone marks above the baseline and hangs vowels below it, so words like ที่, ผู้ and โค้ occupy more vertical space than Latin words of comparable length. Left to line-height: normal, each font resolves that keyword against its own metrics, so identical markup produces a Thai page taller than the English one. Buttons stop lining up, cards in the same grid stop matching, and everything that was composed carefully drifts a little.
The usual workaround, and why it breaks
The common fix is to set a larger font-size for Thai — a pixel or two more when the locale switches.
That holds only while a sentence stays in one script. Thai business writing rarely does. Real sentences look like "ทีม backend ส่ง API ให้ QA แล้ว". The moment both scripts share a line, a rule attached to the page's language cannot help, because the correction is needed within the sentence, not across the document.
font-size-adjust
font-size-adjust: from-font addresses the cause instead.
It reads the aspect value of the first font in the stack — Poppins here — and applies it to every other font in that stack, scaling each until its x-height matches. Noto Sans Thai is enlarged automatically, wherever it appears: a single Thai word in the middle of an English sentence gets the correct size too, because the adjustment happens at the font level rather than the paragraph or page level.
Once the perceived sizes agree, the line heights follow. This site sets line-height as a unitless number — 1.6 for body text, 1.3 for headings — so both scripts resolve leading from the same base rather than from each font's own idea of normal. The Thai page and the English page then genuinely lay out to the same height.
The whole rule sits inside @supports (font-size-adjust: from-font). Browsers without support never see it and get the ordinary, readable page rather than a broken one.
A problem every Thai site has, and almost nobody fixes
None of this is peculiar to this site. Every site carrying both Thai and English has it. Most conclude that Thai simply looks like that and move on.
The result is a great many Thai pages that are slightly harder to read than they need to be — enough that readers tire sooner, not enough that anyone can point at the cause.
The fix is four lines of CSS. The difficult part was never writing them. It was noticing there was a problem at all.
What GIPSIC does here
We build websites and systems that carry two languages seriously, rather than translating strings and swapping a file. Supporting Thai properly is work on type metrics, leading, line breaking and sorting — decisions that belong at the bottom layer of a design system, not in a late pass.
Everything described here is running in the page you are reading. If you would like to see the actual code for any of it, get in touch.
About the author
Film — Wisit. A businessman who still does his own BA work more often than he probably should, and writes a fair bit of code, front and back. Runs two or three small businesses. Follows technology and business obsessively, in Thailand and everywhere else. Off the clock: physics, astronomy, DIY, and anything to do with networks. Music always on, though he cannot sing. Plays instruments anyway, badly. Plays a lot of sport, racket sports above all. One flaw: he barely touches video games.
Written with Claude Opus 5