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.