Home/Learn/HTML, CSS & Responsive/Units — px, rem, em, %, and the Viewport

Units — px, rem, em, %, and the Viewport

Intermediate
CSS Fundamentals

The unit you choose decides whether your layout respects a user who has increased their browser font size. rem for type and spacing, px for hairlines, and the viewport units that actually work on phones.

Overview

Unit choice looks like a style preference and is actually an accessibility decision. A user who sets their browser font to 24px because they cannot read 16px gets nothing from a site built entirely in pixels — the text stays exactly as small as before. rem scales with that setting, which is why type and spacing belong in rem. Beyond that, em is relative to the current element and useful for things that should scale with their own text, percentages are relative to the parent, and viewport units have a well-known mobile problem that dvh exists to fix.

rem, em and px

What each is relative to, and which to reach for.

rem for scale, px for hairlines, em for local
/* rem — relative to the ROOT font size, which the user controls.
   1rem = 16px by default, and 24px if they changed it. */
h1 { font-size: 2rem }          /* scales with the user's setting */
.card { padding: 1.5rem }

/* NEVER do this to make rem maths easier — it halves the text size
   for everyone who changed their browser default: */
html { font-size: 62.5% }       /* the "1rem = 10px" trick. Don't. */

/* em — relative to the CURRENT element's font size. Ideal for
   things that must scale with their own text. */
.button { padding: .5em 1em }   /* padding grows with the label */
.badge  { font-size: .75em }    /* three-quarters of its parent */
/* em compounds through nesting, which is the usual gotcha:
   a .badge inside a .badge is .5625em. */

/* px — fixed. Correct for things that should NOT scale: */
border: 1px solid               /* a hairline is a hairline */
box-shadow: 0 1px 2px

/* The practical rule:
     font-size, margin, padding, gap, border-radius  -> rem
     borders, shadows, hairlines                     -> px
     component-internal spacing that tracks its text -> em     */

Percentages and Viewport Units

Relative to what, exactly — and the 100vh bug on phones.

dvh for phones, ch for line length
/* % is relative to the PARENT, but which dimension varies: */
width: 50%          /* parent's width */
height: 50%         /* parent's HEIGHT — and only if the parent has one */
padding-top: 50%    /* the parent's WIDTH, even vertically.
                       This was the old aspect-ratio hack. */

/* Viewport units */
100vw   /* full viewport width — INCLUDES the scrollbar on desktop,
           which is why width:100vw causes a horizontal scrollbar */
100vh   /* full viewport height */
100svh  /* small: viewport with browser chrome VISIBLE */
100lvh  /* large: chrome hidden */
100dvh  /* dynamic: follows the chrome as it hides and shows */

/* The classic mobile bug: 100vh on iOS is the LARGE viewport, so a
   full-height section is taller than the visible area and its
   bottom is hidden under the address bar. */
.hero { min-height: 100dvh }    /* the fix */

/* ch and ex — relative to the font */
.prose { max-width: 65ch }      /* ~65 characters: the readable line
                                   length, and better than any px value */

Fluid Values

min, max and clamp remove most media queries for sizing.

clamp, min, max, calc
/* Pick the smaller / larger of two values */
width: min(90%, 480px)        /* never wider than 480px, never past 90% */
width: max(50%, 300px)        /* at least 300px */

/* clamp(minimum, preferred, maximum) — the one you will use most */
font-size: clamp(1.5rem, 4vw, 3rem)
padding: clamp(1rem, 5vw, 4rem)
width: clamp(20rem, 50%, 60rem)

/* This replaces three media queries with one line, and it is
   continuous rather than jumping at each breakpoint. */

/* Accessibility caveat: a pure vw preferred value does not scale
   with the user's font setting. Mix in a rem term so it does: */
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.5rem)

/* calc mixes units freely */
width: calc(100% - 2rem)
height: calc(100dvh - var(--header-height))
/* Spaces around + and - are REQUIRED. calc(100%-2rem) is invalid
   and silently does nothing. */

Key Points to Remember

  • 1rem scales with the user's browser font setting, so type and spacing in px ignore an accessibility preference
  • 2The html { font-size: 62.5% } trick halves the text for anyone who changed their default — avoid it
  • 3em is relative to the current element and compounds through nesting
  • 4100vh on phones is the large viewport and hides content under the address bar; dvh follows the chrome
  • 5clamp() replaces sizing media queries, but mix a rem term into the preferred value so it still respects font settings

Interview Questions

Sign in to ask Aria
1

When would you use rem instead of px, and why does it matter?

Medium
2

What is the difference between em and rem?

Easy
3

Why does 100vh cause problems on mobile browsers?

Hard

Ask Aria about Units — px, rem, em, %, and the Viewport

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…