PX vs REM vs EM vs VW: Which CSS Unit to Use

Use rem for font sizes and most spacing, em for spacing that should scale with a component's own text, px for borders and fine details, and vw only inside clamp() for fluid sizing. That combination respects user font settings and zoom while keeping layouts predictable.

The short version

CSS has more than a dozen length units, but four do almost all of the work on a typical site: px, rem, em and vw. The difference between them is what they are measured against.

  • px is an absolute CSS pixel. It does not care about font settings.
  • rem is relative to the font size of the root <html> element, which is 16px by default.
  • em is relative to the font size of the current element (or its parent, when used on font-size itself).
  • vw is 1% of the viewport width.

Picking the right one is mostly about asking: what should this value grow with? The user's text preference, the component's text, the screen, or nothing at all?

px: fixed and predictable

A CSS pixel is not a physical screen pixel. It is a reference unit (defined as 1/96 of an inch at a typical viewing distance), and on high-density screens one CSS pixel is drawn with several device pixels. That is why a 1px border looks just as thin on a phone as on an old monitor.

Px values do scale with browser zoom (Ctrl/Cmd and +), because zoom changes how many device pixels each CSS pixel uses. What px ignores is the default font size in browser settings. If someone with low vision sets their default to 24px, text sized at 16px stays at 16px in most browsers.

Good uses for px: borders, outlines, hairline dividers, box shadows, and small decorative offsets that should never grow.

rem: the default choice for type and spacing

One rem equals the root font size. If you leave the root alone, that is the user's chosen default, usually 16px. So 1.5rem is 24px for most visitors and 36px for someone who set their default to 24px.

That behavior is exactly what you want for body text, headings and the spacing around them. It also makes a design system easy to reason about: a spacing scale of 0.25rem, 0.5rem, 1rem, 2rem stays proportional everywhere, because every value refers to the same root.

Converting from a design file is simple division: rem = px ÷ 16. Our px to rem converter does it for any base size and gives you a full table.

Worked example: converting a design spec

A mockup at the default 16px root specifies 14px captions, 18px body text, 40px headings and 24px card padding.

ElementDesign valueCalculationCSS
Caption14px14 ÷ 160.875rem
Body18px18 ÷ 161.125rem
Heading40px40 ÷ 162.5rem
Card padding24px24 ÷ 161.5rem
Card border1pxkeep as px1px

Avoid setting the root in px (for example html { font-size: 10px }). It overrides the user's preference and removes the main accessibility benefit of rem. If you want the root changed, use a percentage such as 100% or 62.5%, which still scales with the user's setting.

em: sizing relative to the component

Em is relative to the element's own computed font size for properties like padding, margin and width. On the font-size property itself, em is relative to the parent's font size. This makes em ideal for values that should track a component's text: button padding, the gap beside an icon, or the space under a heading.

html { font-size: 100%; }          /* usually 16px, respects user settings */

.card { padding: 1.5rem; }           /* 24px at the default root */

.button {
  font-size: 1.125rem;               /* 18px */
  padding: 0.5em 1em;                /* 9px 18px: scales with this button's text */
}

.button--small { font-size: 0.875rem; }  /* padding shrinks automatically */

Change the button's font size and the padding follows without any extra rules. Use the px to em converter when you need to turn design values into em for a specific parent size.

The compounding trap

Because em on font-size multiplies the parent's size, nested elements can drift.

.menu { font-size: 0.9em; }

/* Three nested .menu lists, 16px root:
   level 1: 16 × 0.9 = 14.4px
   level 2: 14.4 × 0.9 = 12.96px
   level 3: 12.96 × 0.9 ≈ 11.66px   (probably not what you wanted) */

The fix is to set font sizes in rem and keep em for spacing inside a component.

vw and vh: sizing relative to the screen

One vw is 1% of the viewport width; one vh is 1% of its height. On a 1440px-wide window, 5vw is 72px; on a 375px phone it is 18.75px. You can work out any value with the px to vw converter.

Viewport units are great for full-bleed sections and for the "growing" part of fluid sizes. On their own they are risky for text. A heading set to 5vw doesn't respond to the user's font setting, and zooming in on desktop makes the viewport narrower in CSS pixels, so the text can stay the same size or even get smaller. That can fail WCAG 2.2 Success Criterion 1.4.4 (Resize Text).

The safe pattern is to mix vw with rem inside clamp(), so a rem component always responds to zoom and preferences. Our fluid typography guide walks through the maths.

Two other details worth knowing: in most desktop browsers, 100vw includes the vertical scrollbar, so a width: 100vw element can cause a small horizontal scroll. And on mobile, 100vh is usually the height with the browser toolbar hidden, which is why content can end up underneath it.

The supporting cast: %, ch, dvh, svh and lvh

  • % is relative to the parent: the containing block's width for width and horizontal padding, the parent's font size for font-size. It's the natural choice for fluid column widths.
  • ch is the width of the "0" character in the current font. max-width: 65ch gives a readable line length of roughly 60 to 75 characters, depending on the typeface.
  • dvh (dynamic viewport height) follows the visible area as mobile toolbars appear and disappear. svh is the small viewport (toolbars shown) and lvh the large one (toolbars hidden). These units are supported in current versions of all major browsers; check MDN's length reference if you need to support older ones.

A practical tip: dvh changes while the user scrolls, which can make a full-height hero jump. svh is often the calmer choice for a section that should fit on screen.

h1 {
  /* never smaller than 2rem, never larger than 3.5rem,
     grows with the viewport in between */
  font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
}

.hero { min-height: 100svh; }   /* fits under mobile browser toolbars */
.prose { max-width: 65ch; }     /* comfortable line length */

Decision table: which unit to use

What you are sizingBest unitWhy
Body text and headingsrem (or clamp with rem + vw)Respects user font settings and zoom
Page-level spacing, gaps, marginsremConsistent scale tied to the root
Padding inside buttons, badges, inputsemScales with the component's own text
Borders, outlines, dividerspxShould stay thin at every size
Box shadows, small offsetspxDecorative, shouldn't grow with text
Column and container widths%, fr, or max-width in rem/chAdapts to the available space
Readable text measurechTied to character width
Full-height sections on mobilesvh or dvhAccounts for browser toolbars
Fluid type and spacingclamp() with rem + vwGrows with the screen, stays accessible
Media query breakpointsem or px (be consistent)em follows the user's default font size

How zoom and user settings affect each unit

There are two different ways a visitor can make text bigger, and they don't behave the same.

  • Browser zoom scales everything measured in CSS pixels, so px, rem and em all grow. Viewport units don't grow, because zooming changes the viewport size in CSS pixels instead.
  • Default font size (in browser settings) changes the root font size. rem and em follow it; px does not; vw does not.

Some people use both, and some rely on one only. Using rem for text covers both cases, which is why it is the recommended default. Exact behavior can vary slightly between browsers, so test with the font setting raised and zoom at 200% before you ship.

A sensible starting setup

  1. Leave the root font size alone, or set it as a percentage.
  2. Set all font sizes and vertical rhythm in rem.
  3. Use em for padding and gaps inside small components.
  4. Keep px for borders, outlines and shadows.
  5. Use clamp() with a rem + vw middle value when something should grow with the screen.

Follow that, and your layouts will look the way you designed them for most visitors while still adapting for the people who need bigger text.

Frequently asked questions

Is rem better than px?

For font sizes and most spacing, yes. Rem scales with the default font size a user picks in their browser settings, while px ignores that setting. Px is still the better choice for borders, hairlines and small shadows that should never grow.

When should I use em instead of rem?

Use em when a value should scale with the font size of the component itself, such as padding on a button or the gap next to an icon. Use rem when a value should stay tied to the page-wide base size. Avoid em for nested font sizes, because it compounds.

Why is using only vw for font size bad for accessibility?

Text sized only in vw is tied to the window width, so browser zoom and the user font size setting barely change it. That can fail WCAG 1.4.4 Resize Text. Combine vw with rem inside clamp() so text still responds to zoom.

What is the difference between vh and dvh?

On mobile browsers, 100vh usually equals the height of the viewport with the address bar hidden, so content can sit under the toolbar. 100dvh (dynamic viewport height) follows the visible area as the toolbar shows and hides. svh and lvh give the smallest and largest versions.

Tools for this guide