What is the difference between em, rem, %, vw/vh, and px, and when should each be used?

Beginner12 min interview
Skills tested:
Naming what each unit resolves againstExplaining why em compounds when nestedConnecting rem to user font-size preferences and accessibilityKnowing which percentage properties resolve against inline sizeKnowing the viewport unit variants and the mobile 100vh problem

Advertisement

🧩 Scenario

Unit choice is where accessibility quietly succeeds or fails. A design system built on px looks pixel-perfect and completely ignores a user who has set their browser text size to 24px for low vision. One built on rem scales cleanly for that user with no extra work. The same reasoning applies to full-height hero sections, where 100vh and 100dvh look identical on desktop and differ by the height of the browser toolbar on a phone.

Architecture Walkthrough

What Each Unit Resolves Against

Every CSS unit is either absolute or relative, and the relative ones differ only in what they reference.

px is absolute in CSS terms. One CSS pixel is a fixed reference size independent of device pixels, so it does not scale with anything. It is the right choice for values that genuinely should not scale: hairline borders, small fixed offsets, and box shadow spread.

em resolves against the font size of the element itself, except in the font-size property, where it resolves against the parent's font size. That dual behaviour is the source of most em confusion. Its useful property is that it scales with the local type size, which makes it correct for spacing that should track the text: padding inside a button, the gap between an icon and its label, or a margin-bottom proportional to the line.

rem resolves against the root element's font size, which is the html element and defaults to 16px unless the user has changed their browser preference. Because every rem in the document references one value, there is no compounding, and because that value comes from the user's own setting, rem respects text-size preferences automatically. This is why it is the default unit for type and spacing scales in modern design systems.

% resolves against the corresponding dimension of the containing block, with an important exception: padding and margin percentages, including the vertical ones, resolve against the containing block's inline size, not its height. padding-top: 50% therefore produces top padding equal to half the parent's width, which is the mechanism of the classic aspect-ratio hack that aspect-ratio has replaced.

vw and vh resolve against the viewport's width and height. vmin and vmax reference the smaller and larger of the two.

The em Compounding Problem

Because font-size in em resolves against the parent's font size, nested em values multiply.

A .card with font-size: 1.2em inside a 16px body computes to 19.2px. A .title inside it, also 1.2em, computes to 1.2 × 19.2 = 23.04px. Nest a third level and it grows again. The individual declarations look modest and consistent, and the result depends entirely on how deeply the component happens to be nested, which is not something a component author can know.

rem eliminates this by referencing the root every time. 1.2rem is 19.2px regardless of nesting depth. The practical rule most design systems settle on is rem for font sizes and em for spacing that should scale relative to the local font size.

The px Accessibility Problem

Setting font-size in px produces text that ignores the user's browser font-size preference in most engines. A user who has set their default text size to 24px for a visual impairment sees no change on a px-based site, while page zoom still works but scales the entire layout rather than just text.

Using rem for type means the whole scale shifts when the user changes their preference, which is the behaviour assistive settings are designed to produce. This is the strongest single argument for rem and the reason it is worth stating as a rule rather than a preference.

Related trap: the html { font-size: 62.5% } trick, once popular so that 1rem equals 10px for easier arithmetic. It still respects proportional changes to the user's preference, but it rescales everything against a 10px base, which makes the design's relationship to the user's chosen size less predictable and interacts badly with third-party components assuming a 16px root. Prefer leaving the root alone and using a type scale in rem.

The 100vh Mobile Problem

vh refers to the largest viewport height, which on mobile means the height with browser chrome collapsed. A 100vh section therefore extends beneath the visible toolbar and address bar, producing content cut off at the bottom and often an unexpected scrollbar.

The viewport unit variants fix this. dvh tracks the dynamic viewport and changes as the chrome shows and hides, svh is the small viewport with chrome visible, and lvh is the large viewport, equivalent to the classic vh. For a full-height hero or app shell, 100dvh is the correct default, with min-height rather than height so content longer than the viewport can still extend.

Note that dvh changing during scroll can cause layout to shift as the toolbar collapses, so for cases where stability matters more than exactness, svh is the safer choice.

Choosing

Use rem for font sizes and for a spacing scale. Use em for spacing that should track the local font size, and for media query widths where it respects font preferences better than px. Use % for widths within a containing block. Use dvh/svh for full-height sections, and vw sparingly, usually inside clamp() rather than alone. Use px for borders, shadows, and other details that genuinely should not scale.


Key Code Explained

/* px: fixed, does not scale — correct for hairlines and shadows */
.card {
  border: 1px solid #e2e8f0;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}

/* em COMPOUNDS in font-size, because it resolves against the PARENT */
body  { font-size: 16px; }
.card  { font-size: 1.2em; } /* 16 x 1.2   = 19.2px  */
.title { font-size: 1.2em; } /* 19.2 x 1.2 = 23.04px */
/* Same declarations, different results, purely from nesting depth. */

/* rem always references the ROOT — no compounding */
.card-rem  { font-size: 1.2rem; } /* 19.2px */
.title-rem { font-size: 1.5rem; } /* 24px, whatever the nesting */

/* em is RIGHT for spacing that should track the local type size */
.button {
  font-size: 1rem;
  padding: 0.75em 1.5em; /* scales automatically with any font-size change */
  gap: 0.5em;
}
.button--large {
  font-size: 1.25rem; /* padding scales with it, no extra declarations */
}

/* Percentage padding resolves against the containing block's INLINE size */
.legacy-aspect-ratio {
  width: 100%;
  padding-top: 56.25%; /* 9/16 of the WIDTH, not the height */
}
.modern-aspect-ratio {
  aspect-ratio: 16 / 9; /* what you should write today */
}

/* 100vh OVERFLOWS on mobile: vh is the LARGE viewport */
.hero-broken {
  height: 100vh;
}
/* dvh tracks the dynamic viewport; min-height lets content grow */
.hero {
  min-height: 100dvh;
}
/* svh is the small viewport — stable, no shift when chrome collapses */
.hero-stable {
  min-height: 100svh;
}

/* em in media queries respects the user's font-size preference */
@media (min-width: 48em) { /* 768px at a 16px root, scales if the user changes it */
  .layout { grid-template-columns: 240px 1fr; }
}

/* vw is best used inside clamp() rather than alone */
.fluid-heading {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

The em compounding block is the example to be able to compute on the spot. Both declarations say 1.2em and both look reasonable in isolation, yet the title ends up at 23.04px through nothing but nesting. That is the concrete case for rem in type scales: a component author cannot predict how deeply their component will be placed.

The .button and .button--large pair shows the other side of the argument. Using em for padding means a single font-size change produces a correctly proportioned larger button with no other declarations, which is exactly the behaviour you want inside a component and exactly the behaviour rem would prevent.


Tradeoffs

UnitResolves againstCompoundsRespects user font sizeBest for
pxNothing; fixedNoNoBorders, shadows, hairlines
emOwn font size; parent's for font-sizeYesIndirectlySpacing that tracks local type, media queries
remRoot font sizeNoYesType scale, spacing scale
%Containing block; inline size for padding/marginNoNoWidths, fluid columns
vw / vhViewport, large variantNoNoUse inside clamp(); vh is unsafe on mobile
dvh / svh / lvhDynamic / small / large viewportNoNoFull-height sections

What Interviewers Actually Check

  • Whether you can say what each unit resolves against, including the em exception in font-size
  • Whether you can work through an em compounding example numerically
  • Whether you connect px font sizes to a real accessibility failure rather than calling it a style preference
  • Whether you know percentage padding resolves against inline size
  • Whether you know why 100vh breaks on mobile and can name the replacement

Follow-Up Questions

  1. Why does em behave differently in font-size than in every other property?
  2. What are ch and ex units, and where is ch the most appropriate choice?
  3. How do vmin and vmax behave differently on a landscape tablet versus a portrait phone?
  4. Why can dvh cause layout shift during scroll, and when would svh be preferable despite being smaller?
  5. How does container-type: inline-size unlock cqw and cqi units, and how do they compare to vw?

Common Candidate Mistakes

  • Setting font sizes in px and breaking browser text-size scaling for users who rely on it, then treating page zoom as an equivalent substitute
  • Nesting em font sizes across component levels, producing compounding growth that depends on where the component happens to sit in the tree
  • Writing height: 100vh for full-screen sections, which overflows on mobile because vh is the large viewport height measured with browser chrome collapsed
  • Assuming padding-top: 10% resolves against the container's height, when all padding and margin percentages resolve against inline size
  • Applying html { font-size: 62.5% } for convenient arithmetic, which rebases the whole design against a 10px root and interacts badly with third-party components expecting 16px

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you state what each unit resolves against, including the em exception in font-size?
  • Can you work through the em compounding problem numerically for two nesting levels?
  • Can you explain why rem is the accessible default for type?
  • Can you explain why 100vh overflows on mobile and name the unit that fixes it?
  • Can you pick the right unit for type, internal spacing, borders, and full-height sections?

Summary

CSS units differ only in what they reference. px is fixed and scales with nothing, which suits borders, shadows, and other details that should stay constant. em resolves against the element's own font size, except in font-size where it resolves against the parent's, and that exception is why nested em font sizes compound: 1.2em inside 1.2em inside a 16px body yields 23.04px. rem always references the root font size, so it never compounds and, because the root reflects the user's browser preference, it respects text-size accessibility settings that px ignores.

Percentages resolve against the containing block's corresponding dimension, with the notable exception that padding and margin percentages, including vertical ones, resolve against inline size. That is why padding-top: 56.25% once served as an aspect-ratio hack, a job aspect-ratio now does directly. Viewport units reference the viewport, and the plain vh refers to the large viewport measured with mobile browser chrome collapsed, which is why 100vh overflows on phones. dvh tracks the dynamic viewport, svh the small one, and min-height: 100dvh or 100svh is the correct form for a full-height section.

The working rules are straightforward: rem for type and spacing scales, em for spacing that should track the local font size and for media query widths, % for widths within a container, dvh or svh for full-height regions, vw inside clamp() rather than alone, and px for the details that genuinely must not scale.

Frequently Asked Questions

Why is px bad for font sizes?

A px font size does not scale with the user browser font-size preference in most engines, so users who increase their default text size see no change. rem respects that preference.

What is the difference between vh and dvh?

vh uses the largest viewport height, which on mobile ignores the collapsing browser chrome, so 100vh overflows. dvh tracks the dynamic viewport and is the correct choice for full-height layouts.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement