CSS has more length units than most people use, and interviews tend to probe the handful that matter: why rem instead of px for text, what goes wrong with viewport units, and how to make type scale with the screen without breaking zoom. Each unit answers a different question: relative to what should this size be? Picking the unit is really picking the thing it should follow.
Absolute and font-relative units
px is a CSS pixel, a fixed reference size (not a device pixel; a 3× phone draws one CSS pixel with nine device pixels). It’s right for things that shouldn’t scale with text: hairline borders, shadow offsets, small fixed gaps in icon layouts.
rem is relative to the root element’s font size. By default, browsers set that to 16px, so 1rem is 16px and 1.5rem is 24px. But users can change the default in their browser settings (Chrome’s “Font size” setting, Firefox’s “Default font size”). Someone with low vision might choose 20px or 24px. With font-size: 1rem, your body text follows their choice. With font-size: 16px, it doesn’t. That’s the main reason to use rem for type.
Note that browser zoom (Ctrl/Cmd and +) scales everything, including px, so px text does get bigger with zoom. It’s the default-font-size setting that px ignores. People confuse the two in interviews, and the distinction is worth stating precisely.
em is relative to the current element’s font size (or, when used for font-size itself, the parent’s). It compounds: a list with font-size: 0.875em nested three deep ends up at 0.875³ ≈ 0.67 of the outer size. That makes em awkward for type but handy for spacing that should scale with a component’s own text:
.button {
font-size: 1rem;
padding: 0.5em 1em;
}
.button.small {
font-size: 0.875rem;
}The small button’s padding shrinks with its text automatically (7px by 14px instead of 8px by 16px) because em follows the button’s font size.
ch is the width of the “0” glyph in the current font. It’s the natural unit for line length:
.article {
max-width: 65ch;
}That caps lines at around 65 characters, the range typographers recommend for comfortable reading, and it adjusts automatically if the font or font size changes. In a typical proportional font, 65ch is a bit wider than 65 average characters, since “0” is wider than many letters, but it’s close enough.
Percentages and constraints instead of fixed widths
% is relative to the containing block (for widths, its width). The point of percentages and max-width is to let content shrink. A common anti-pattern:
.card {
width: 600px;
}On a 375px phone, the card overflows by 225px plus padding. The constraint version:
.card {
width: 100%;
max-width: 600px;
}or, combining both in one declaration, width: min(100%, 600px). On the phone it’s 375px (minus whatever padding the parent has); on a laptop it stops at 600px. The fixed 600px was never really the requirement. The requirement was “no wider than 600px”, and max-width says exactly that.
Viewport units and the mobile toolbar problem
vw and vh are 1% of the viewport’s width and height. 100vw is the full viewport width, which includes the vertical scrollbar on desktop, so width: 100vw on a page with a scrollbar overflows horizontally by the scrollbar’s width (often 15px on Windows). width: 100% on a block element avoids that.
On mobile, 100vh has a different problem. Mobile browsers show and hide their address bar and toolbar as you scroll. vh was defined as the largest possible viewport (toolbars hidden), so a height: 100vh hero is taller than the visible screen when the page loads with toolbars showing, and its bottom (often the call-to-action button) sits behind the toolbar.
The newer units name the viewport you mean:
svh: small viewport height, with toolbars shown.100svhalways fits on screen.lvh: large viewport height, toolbars hidden. Same as the oldvh.dvh: dynamic, updates as toolbars move. Accurate, but changes size during scrolling, which can make content jump.
For a full-screen hero, min-height: 100svh is usually the right choice. All current browsers support these units; for older ones, put min-height: 100vh on the line before it as a fallback.
Fluid type with clamp()
clamp(min, preferred, max) returns the preferred value, kept between the minimum and maximum. For an article’s intro paragraph that grows a little with the screen:
.intro {
font-size: clamp(1rem, 0.875rem + 0.5vw, 1.25rem);
}The preferred value is 14px + 0.5vw (with the default 16px root). Evaluated at real widths:
viewport 0.875rem + 0.5vw clamped result
320px 15.60px 16px (minimum)
768px 17.84px 17.84px
1000px 19.00px 19px
1280px 20.40px 20px (maximum)Why mix rem into the preferred value instead of just writing 5vw? Because vw doesn’t respond to zoom the way people expect. At 200% zoom, the CSS viewport of a 1280px window becomes 640px wide, so vw values halve in CSS pixels. Text set in pure vw is drawn at the same physical size: zoom does nothing, which fails WCAG 1.4.4 (Resize Text). With the mixed formula, the 1280px window at 200% zoom computes 14 + 3.2 = 17.2 CSS pixels, drawn at 34.4 device pixels instead of 20. That’s 1.72 times larger, not quite 2, which is why the vw part should stay small relative to the rem part, and why you should test at 200%. Keeping the maximum within about 2.5 times the minimum also helps.
Tools generate these formulas for a chosen size range, but you should be able to explain what each part does.
A layout, worked through
A blog post page combining all of this:
:root {
font-size: 100%; /* respect the user's default, don't force 16px or 62.5% */
}
body {
font-size: 1rem;
line-height: 1.6;
margin: 0;
}
.post {
max-width: 65ch;
margin-inline: auto;
padding-inline: clamp(1rem, 4vw, 2rem);
}
.post h1 {
font-size: clamp(1.75rem, 1.25rem + 2vw, 2.75rem);
line-height: 1.15;
}
.hero {
min-height: 100vh;
min-height: 100svh;
}
hr {
border: 0;
border-top: 1px solid #e4e4e7;
}line-height: 1.6 is unitless on purpose: unitless line heights are inherited as a ratio, so each element computes its own line height from its own font size. line-height: 1.6em would be computed once on body and inherited as a fixed length, so a 2.75rem heading would get body-text line spacing and its lines would overlap.
The old html { font-size: 62.5%; } trick (making 1rem equal 10px for easy maths) still respects user settings because it’s a percentage, but it forces you to set body text to 1.6rem everywhere, and third-party components that assume 1rem is 16px render tiny. Most teams now leave the root alone.
Interview practice
A marketing page sets h1 { font-size: 6vw; }, the body text to 14px, and the hero to height: 100vh. Users report that the heading doesn’t get bigger when they zoom, that text is too small for some, and that the hero’s button is hidden behind Safari’s toolbar on iPhones. Fix each issue and explain why.
Answer
The heading uses pure viewport units, so zooming shrinks the CSS viewport and 6vw shrinks in CSS pixels by the same factor; the drawn size stays constant. Replace it with a clamped mix, such as clamp(2rem, 1.5rem + 2.5vw, 3.5rem), so the rem part scales with zoom and the user’s font setting. The body text in px ignores users’ default font size setting, so someone who set 20px gets 14px anyway; use 1rem (or 0.875rem if the design truly needs it smaller, though 1rem is the better default for body copy). For the hero, 100vh is the large viewport on mobile, taller than the visible area when toolbars show; change it to min-height: 100svh with min-height: 100vh before it as a fallback, and use min-height instead of height so long content can still grow the section. Then test at 200% zoom and with a larger default font size, and on a real phone with toolbars visible.