CSS CSS Units (px, em, rem, %, and more)
Every length value in CSS — a width, a font-size, a margin — needs a unit to say what kind of measurement it is. CSS gives you far more choices than just pixels: absolute units that never change, and relative units that scale based on the font size, the viewport, or a parent element. Picking the right unit is one of the most important skills for building layouts that stay readable and responsive across devices, zoom levels, and user preferences.
This lesson covers every commonly used CSS unit, how the browser actually resolves each one during layout, where each shines, and the mistakes developers make most often when choosing between them.
Overview / How it works
CSS units fall into two broad families:
- Absolute units (
px,in,cm,mm,pt,pc,q) represent a fixed physical or pixel measurement. They do not change based on anything else on the page.pxis by far the most common of these on screens; the others (inches, centimeters, points) matter mostly for print stylesheets. - Relative units (
em,rem,%,vw,vh,vmin,vmax,ch,ex) are computed relative to something else: the current element’s font size, the root element’s font size, a parent’s size, or the size of the browser viewport. The browser resolves these into an actual pixel value only at layout time, which is what makes them so powerful for responsive design.Here is the key detail most tutorials skip: a CSS pixel is not a physical pixel. A CSS
pxis a reference unit, roughly 1/96th of an inch, that the browser maps onto your device’s actual screen pixels using the device’s pixel ratio. That is why a1pxborder looks reasonably similar in size whether you are on a laptop or a high-density phone screen, even though the phone might have 3 physical pixels for every 1 CSS pixel.The rendering engine treats font-size-relative units (
em,rem,ch,ex) very differently from viewport-relative units (vw,vh,vmin,vmax). Font-relative units are resolved during the same style-computation pass that determines each element’s computed font size — and crucially,emis resolved against the font size of the current element (or, forfont-sizeitself, the parent’s font size), which lets it compound across nested elements.remsidesteps that compounding by always referring to the root (<html>) element’s font size, no matter how deeply nested the element is. Percentages are resolved against whatever the CSS specification defines as the reference for that particular property — forwidth, that is the containing block’s width; forfont-size, it is the parent’s computed font size; forline-height, it is the element’s own font size. There is no single universal rule for what a percentage means — it is property-specific.Syntax
A length value is always a number immediately followed by a unit identifier, with no space between them (except for the value
0, which never needs a unit at all):property: <number><unit>; property: <number>%;Unit Category Resolves relative to pxAbsolute Fixed CSS reference pixel in,cm,mm,pt,pc,qAbsolute Fixed physical measurement (mostly print) emRelative Current element’s (or parent’s, for font-size) computed font sizeremRelative Root ( <html>) element’s font size%Relative Depends on property — often the containing block vw/vhRelative 1% of viewport width / height vmin/vmaxRelative Smaller / larger of vwandvhchRelative Width of the "0"character in the current fontexRelative Height of a lowercase "x"in the current fontExamples
Example 1: px vs. rem for font sizing
html { font-size: 100%; /* respects user's browser font-size setting */ } h1 { font-size: 2.5rem; /* 2.5 * root font size */ } p { font-size: 1rem; line-height: 1.6; } .badge { font-size: 12px; /* fixed label size, intentionally not scaling */ }Result: The heading renders at 2.5 times the root font size (40px if the root is 16px, but it will scale up or down if the user changes their browser’s default font size). The paragraph text renders at exactly the root font size with comfortable line spacing. The small badge label stays a fixed 12px regardless of the user’s font-size preference.
This shows the core tradeoff:
remlets typography respect user preferences and accessibility zoom settings, whilepxis appropriate for the rare cases where you truly want a fixed size (like a small icon badge) that shouldn’t grow with user settings.Example 2: Percentages for a fluid layout container
.page { width: 100%; max-width: 75rem; /* caps the container on very wide screens */ margin: 0 auto; padding: 0 1.5rem; } .sidebar { width: 30%; float: left; } .main-content { width: 70%; float: left; }Result: The
.pagecontainer stretches to fill its parent’s full width on small screens, but stops growing once it reaches 1200px (75rem at a 16px root), staying centered with side padding on very wide monitors. Inside it, the sidebar occupies 30% of the container’s width and the main content occupies the remaining 70%, both floated side by side.The percentages here are resolved against
.page‘s computed width, which is itself resolved against its own parent — percentages always cascade down through the containing-block chain.Example 3: Fluid typography with clamp(), viewport units, and ch
.hero-title { font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem); line-height: 1.1; } .article-body { max-width: 65ch; margin-inline: auto; font-size: 1.0625rem; line-height: 1.65; }Result: The hero title scales smoothly with the viewport width — never smaller than 1.75rem, never larger than 3.5rem, growing fluidly between those bounds as the browser window resizes. The article body is capped at roughly 65 characters per line (using
ch, which measures the width of the "0" character in the current font), which is close to the ideal reading-line-length recommended by typography research, and it stays centered viamargin-inline: auto.clamp(min, preferred, max)combines a fixed floor and ceiling with a fluid middle value built from viewport units, giving you responsive type without a single media query.How it works step by step
- Step 1 — Cascade and specificity resolve which declaration wins for a given property on a given element, exactly as with any other CSS value.
- Step 2 — The browser computes the root font size, defaulting to 16px unless the user or an
html { font-size: ... }rule changes it. - Step 3 — Font-relative units are resolved top-down through the DOM tree. For each element, the browser first determines its own computed font size (which may itself be defined in
emorrem, relative to its parent or the root), then uses that resolved font size to convert anyem,ch, orexvalues on that same element into pixels. - Step 4 — Percentages are resolved against the specific reference defined for that property by the CSS spec (containing block width for
width/margin/padding, line box forline-height, and so on), which requires the browser to already know the parent’s final size — this is why deeply nested percentage widths can cascade unexpected sizing if an ancestor’s width is itself undefined (auto). - Step 5 — Viewport units are resolved against the viewport’s current size at render time, and are recalculated whenever the viewport is resized, independent of the DOM tree entirely.
- Step 6 — All units are converted to a common pixel value before the layout algorithm (box model, flex, grid) runs, since layout math needs a single absolute unit to work with.
Common Mistakes
Mistake 1: Using px for font-size everywhere
body { font-size: 16px; } h2 { font-size: 24px; } p { font-size: 16px }This looks harmless, but hardcoding every font size in
pxignores the user’s browser font-size preference in some browsers’ zoom implementations, which can make text hard to read for users who rely on larger default fonts for accessibility. It also makes the whole type scale harder to adjust in one place.Corrected version — set the root size once, then use
remso the whole scale can shift together:html { font-size: 100%; } h2 { font-size: 1.5rem; } p { font-size: 1rem; }Mistake 2: Unintentional em compounding in nested elements
.list { font-size: 1.2em; } .list .list { font-size: 1.2em }Because
emresolves against the parent’s computed font size, nesting two lists with this rule doesn’t give you 1.2x the base size — it gives you 1.2 × 1.2 = 1.44x, and a third level of nesting would compound again to roughly 1.73x. This silent compounding is a classic source of "why is my text huge three levels deep" bugs.Corrected version — use
remso nested elements don’t multiply against each other:.list { font-size: 1.2rem; } .list .list { font-size: 1.2rem; }Best Practices
- Use
remfor font sizes and most spacing (margin,padding,gap) so the whole design scales predictably from one root font-size change. - Reserve
emfor values that should genuinely scale with a specific element’s own font size, such as padding inside a button that should grow proportionally with the button’s text. - Use
%for widths inside fluid, container-based layouts, paired withmax-widthto prevent content from stretching too wide on large screens. - Use
vw/vhsparingly and prefer wrapping them inclamp()for fluid typography, so text never becomes unreadably small or absurdly large. - Use
chto constrain paragraph width to a comfortable reading measure (typically 45–75 characters). - Avoid raw
pxfor font sizes; keep it for things that should truly stay fixed, like a 1px border or a small fixed icon. - Test your layout with the browser’s text-zoom and page-zoom features, not just by resizing the window, since these stress different units in different ways.
Practice Exercises
- Exercise 1: Build a type scale where
htmlsets the root font size, andh1throughh4useremvalues that form a clear visual hierarchy (for example 2.5rem, 2rem, 1.5rem, 1.25rem). Confirm that changing only the root font size scales every heading proportionally. - Exercise 2: Create a card component with a
widthof100%and amax-widthof25rem, then place three cards inside a flex container. Observe how the cards shrink on narrow viewports but stop growing past their max-width on wide ones. - Exercise 3: Write a fluid heading using
clamp()that stays between1.5remand3rem, scaling with5vwin between. Resize your browser window and describe at what point the heading stops growing.
Summary
pxis an absolute, fixed unit — predictable but unresponsive to user font preferences.emis relative to the current (or parent, forfont-size) element’s font size, and can compound when nested.remis relative only to the root element’s font size, avoiding compounding — the default choice for most typography and spacing.%is resolved differently per property, most often against a containing block’s size — great for fluid widths.vw,vh,vmin, andvmaxare relative to the viewport, ideal for full-bleed layout sections and fluid type when combined withclamp().chandexare tied to font glyph metrics, useful for constraining text measure.- All units are converted to pixels before the layout algorithm runs, but which unit you choose determines how your design responds to zoom, user settings, and viewport changes.
