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. px is 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 px is 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 a 1px border 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, em is resolved against the font size of the current element (or, for font-size itself, the parent’s font size), which lets it compound across nested elements. rem sidesteps 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 — for width, that is the containing block’s width; for font-size, it is the parent’s computed font size; for line-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
    px Absolute Fixed CSS reference pixel
    in, cm, mm, pt, pc, q Absolute Fixed physical measurement (mostly print)
    em Relative Current element’s (or parent’s, for font-size) computed font size
    rem Relative Root (<html>) element’s font size
    % Relative Depends on property — often the containing block
    vw / vh Relative 1% of viewport width / height
    vmin / vmax Relative Smaller / larger of vw and vh
    ch Relative Width of the "0" character in the current font
    ex Relative Height of a lowercase "x" in the current font

    Examples

    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: rem lets typography respect user preferences and accessibility zoom settings, while px is 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 .page container 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 via margin-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 em or rem, relative to its parent or the root), then uses that resolved font size to convert any em, ch, or ex values 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 for line-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 px ignores 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 rem so 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 em resolves 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 rem so nested elements don’t multiply against each other:

    .list {
      font-size: 1.2rem;
    }
    
    .list .list {
      font-size: 1.2rem;
    }

    Best Practices

    • Use rem for font sizes and most spacing (margin, padding, gap) so the whole design scales predictably from one root font-size change.
    • Reserve em for 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 with max-width to prevent content from stretching too wide on large screens.
    • Use vw/vh sparingly and prefer wrapping them in clamp() for fluid typography, so text never becomes unreadably small or absurdly large.
    • Use ch to constrain paragraph width to a comfortable reading measure (typically 45–75 characters).
    • Avoid raw px for 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 html sets the root font size, and h1 through h4 use rem values 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 width of 100% and a max-width of 25rem, 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 between 1.5rem and 3rem, scaling with 5vw in between. Resize your browser window and describe at what point the heading stops growing.

    Summary

    • px is an absolute, fixed unit — predictable but unresponsive to user font preferences.
    • em is relative to the current (or parent, for font-size) element’s font size, and can compound when nested.
    • rem is 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, and vmax are relative to the viewport, ideal for full-bleed layout sections and fluid type when combined with clamp().
    • ch and ex are 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.