Toolmingo
Guides8 min read

CSS Units Guide: px, em, rem, vw, vh, % and When to Use Each

Master every CSS unit — absolute (px, pt), font-relative (em, rem, ch), viewport (vw, vh, dvh), and percentage. Includes a cheat sheet, real-world patterns, and the six mistakes that cost you hours of debugging.

Choosing the wrong CSS unit breaks responsive layouts, makes accessibility settings useless, and causes components to behave differently on every screen. This guide explains every unit type clearly, tells you exactly when to reach for each one, and covers the six mistakes that trip up developers at every level.


Quick reference

Unit Type Relative to… Best for…
px Absolute Device pixels (1/96 inch) Borders, shadows, icons
pt Absolute Points (1/72 inch) Print stylesheets
em Font-relative Current element's font-size Component-level spacing
rem Font-relative Root (html) font-size Global spacing, typography
% Relative Parent element Fluid widths, heights
vw Viewport 1% of viewport width Fluid type, full-bleed sections
vh Viewport 1% of viewport height Hero sections, modals
dvw / dvh Dynamic viewport Visible area (mobile browser) Mobile hero sections
vmin / vmax Viewport Smaller / larger viewport axis Square elements
ch Font-relative Width of 0 character Readable text columns
ex Font-relative Height of lowercase x Fine-tuning vertical rhythm
cqw / cqh Container Container query dimension Container-aware components

Absolute units

Absolute units have a fixed physical size regardless of anything else.

px — pixels

.card {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
}

px is the most used unit on the web. One CSS pixel equals 1/96 of an inch — but on high-DPI (Retina) screens, the browser uses multiple physical pixels to render one CSS pixel. This is handled automatically; you always write logical CSS pixels.

Use px for: borders, border-radius, box-shadow offsets, icon sizes, and any value that should stay crisp regardless of font-size preferences.

Avoid px for: font-size, padding, margin, and width where you want the layout to scale with user preferences.

pt, cm, mm, in

These are true physical units designed for print stylesheets (@media print). Never use them for screen layouts — their rendering is inconsistent across screens and operating systems.


Font-relative units

Font-relative units scale with text size, which makes them essential for accessible layouts.

em — relative to current font-size

.button {
  font-size: 1rem;       /* 16px */
  padding: 0.5em 1em;   /* 8px top/bottom, 16px left/right */
}

.button--large {
  font-size: 1.25rem;    /* 20px */
  /* padding auto-scales: 10px top/bottom, 20px left/right */
}

em compounds. If a child element inside a 0.8em container sets font-size: 0.8em, the result is 0.8 × 0.8 = 0.64em — 64% of the root size. This compounding makes em great for component-internal spacing (padding, gap, margin-inline) but unpredictable for font-size itself.

Use em for: padding and margin inside a component, so the component scales as one unit when its font-size changes.

rem — relative to root font-size

:root {
  font-size: 16px; /* browser default — don't override this */
}

h1 { font-size: 2rem; }    /* 32px */
p  { font-size: 1rem; }    /* 16px */
.sidebar { width: 16rem; } /* 256px */

rem always refers to the html element's font-size. It never compounds. When a user changes their browser's default font size (say, to 20px for accessibility), all rem values scale accordingly — 1rem becomes 20px, 2rem becomes 40px.

Critical rule: Never set html { font-size: 62.5% } (the old "10px trick"). It overrides the user's font preference and breaks accessibility.

Use rem for: font sizes, global spacing tokens, max-width values, anything that should respect the user's browser preference.

ch — width of the 0 character

.prose {
  max-width: 65ch; /* ~65 characters wide — ideal for reading */
  margin-inline: auto;
}

1ch equals the advance width of the 0 (zero) character in the current font. It varies by font and weight — it is not exactly one character wide for every glyph. Use it to set readable text column widths (50–75ch is the typographic ideal).

ex — height of lowercase x

Rarely used. Occasionally useful for fine-tuning vertical alignment of inline elements like icons next to text. em is usually sufficient.


Percentage %

Percentage values are relative to the parent element — but which dimension depends on the property:

Property % relative to…
width Parent's width
height Parent's height (parent must have a defined height)
padding Parent's width (even for top/bottom padding)
margin Parent's width (even for top/bottom margin)
font-size Parent's font-size
transform: translate(50%) Element's own size
.container {
  width: 90%;
  max-width: 1200px;
  margin-inline: auto;
}

.hero {
  padding-block: 10%; /* 10% of container's width, not height */
}

The height % trap: height: 50% only works if the parent has an explicit height. If the parent is height: auto, percentage heights collapse to zero.


Viewport units

Viewport units are relative to the browser window, not the element or its parent.

vw and vh

/* Classic full-screen hero */
.hero {
  width: 100vw;
  height: 100vh;
}

/* Fluid heading that scales with window width */
.display-title {
  font-size: clamp(2rem, 5vw, 4rem);
}

100vw includes the scrollbar width on some browsers, causing horizontal overflow. Use 100% for widths instead — and reserve vw for font-size with clamp().

dvh, dvw — dynamic viewport units

On mobile browsers, the browser UI (address bar) appears and disappears as you scroll. vh measures the viewport including the collapsed bar, which makes 100vh taller than the visible area on page load.

/* Wrong on mobile — address bar covers content */
.hero { height: 100vh; }

/* Correct — tracks visible area */
.hero { height: 100dvh; }

Use dvh for mobile hero sections and bottom sheets. Fall back to 100vh for browsers that don't support dynamic units (use @supports or CSS cascade).

svh, lvh — small and large viewport

  • svh = smallest possible viewport (UI fully visible)
  • lvh = largest possible viewport (UI hidden/collapsed)
  • dvh = current actual viewport (changes as UI shows/hides)

For static layouts that should never be cut off: svh. For full-bleed animations: lvh.

vmin and vmax

.square-avatar {
  width: 20vmin;
  height: 20vmin; /* square regardless of orientation */
}

vmin = the smaller of vw and vh. vmax = the larger. Useful for elements that must remain square or proportional across portrait/landscape.


Container query units cqw, cqh

Container query units are relative to the container, not the viewport. They require a container-type on an ancestor element.

.card-grid {
  container-type: inline-size;
}

.card-title {
  font-size: clamp(1rem, 4cqw, 1.5rem);
  /* scales with card width, not page width */
}
Unit Relative to…
cqw Container's inline size (width in horizontal writing)
cqh Container's block size (height)
cqi Container's inline size (writing-mode aware)
cqb Container's block size (writing-mode aware)
cqmin Smaller of cqi and cqb
cqmax Larger of cqi and cqb

Container query units are ideal for component libraries where a card must be responsive to its column width, not the viewport.


Real-world patterns

Global spacing scale with rem

:root {
  --space-1: 0.25rem;  /*  4px */
  --space-2: 0.5rem;   /*  8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */
}

.card {
  padding: var(--space-4);
  margin-block: var(--space-6);
}

Self-scaling component with em

.badge {
  font-size: 0.75rem;
  padding: 0.25em 0.6em;  /* scales if font-size changes */
  border-radius: 999px;
}

.badge--large {
  font-size: 1rem; /* padding auto-scales */
}

Fluid typography with clamp()

body {
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
}

h1 {
  font-size: clamp(1.75rem, 5vw + 1rem, 3.5rem);
}

clamp(min, preferred, max) produces a value that grows with viewport width but never goes below min or above max. No media queries needed.

Readable prose column

.article-body {
  max-width: 70ch;
  margin-inline: auto;
  font-size: 1.125rem;
  line-height: 1.7;
}

6 common mistakes

Mistake Problem Fix
html { font-size: 62.5% } Overrides user's accessibility preference Remove it; use rem directly
font-size in px Ignores browser zoom and font preferences Use rem for all font sizes
height: 100% without parent height Collapses to zero Set explicit height on parent, or use min-height: 100dvh
100vw for full-width divs Adds horizontal scroll (includes scrollbar) Use width: 100% instead
padding in px inside scalable components Doesn't scale when font-size changes Use em for internal component spacing
vh for mobile hero sections Address bar cuts off content on load Use dvh with vh fallback

FAQ

What is the default font-size in browsers?
16px. This is the baseline for 1rem. Never change html { font-size } — it breaks accessibility for users who set a custom base size.

Should I use px or rem for spacing?
Use rem for spacing that should scale with user font preferences (padding, margin, gap in layouts). Use px for decorative values that should stay fixed (border widths, box-shadow blurs, 1–2px details).

When should I use em instead of rem?
Inside a self-contained component where padding, gap, and border-radius should all scale together when the component's font-size changes. For example, a <Button> whose internal spacing grows when font-size: 1.25rem is set.

Is dvh safe to use?
Browser support is excellent as of 2024 (Chrome 108+, Safari 16+, Firefox 110+). Provide a vh fallback: height: 100vh; height: 100dvh; — browsers that don't recognise dvh ignore the second declaration.

What unit should I use for border-radius?
px for small radii (4–16px). % for circles (50%). rem if the radius should scale with font size (pill buttons). Using px for radius on cards is conventional and expected.

What is 1ch in pixels?
It depends on the font. In the browser's default font (usually around 16px), 1ch ≈ 8–9px. It is not a universal character width — it measures only the 0 glyph. For body text, 65ch ≈ 520–585px, which keeps lines at the readable 65-character target.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools