CSS Units Explained: px vs rem vs em vs vw
This guide has a free tool → Open CSS Units Converter
# CSS Units Explained: px vs rem vs em vs vw
The CSS Units Problem
You are styling a heading and you type font-size: 24px. It looks great on your monitor. Then you check it on a phone and it is either too big or too small. A teammate changes the base font size and suddenly half the spacing is off. You add a media query to fix the heading, but now you need another for the paragraph text, and another for the padding, and suddenly you have three breakpoints in your CSS for what should have been a single declarative rule.
CSS has over a dozen unit types. Picking the right one is not just about personal preference. It directly affects responsiveness, accessibility, and maintainability. A codebase that uses px everywhere will fight you when you try to make it responsive or accessible. A codebase that uses the right unit for each context almost adapts itself.
This guide covers every unit that matters in real-world CSS development, explains when and why to use each one, and shows you the modern techniques that combine them for the most flexible results.
---
Absolute Units
Absolute units have a fixed physical size that does not change based on context. In CSS, px is the absolute unit you will use almost exclusively. The others (cm, mm, in, pt, pc) are useful for print stylesheets but rarely appear in screen layouts.
px (Pixels)
One px in CSS is not necessarily one physical pixel on the screen. On high-DPI (Retina) displays, the browser maps CSS pixels to multiple physical pixels to keep sizes visually consistent across screens. CSS handles this for you, so you do not need to think about device pixel ratios in most cases. A CSS px behaves as you would expect: a consistent, fixed size unit.
.button {
padding: 12px 24px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}When to use px:
- Borders and outlines (you almost always want a consistent 1px or 2px line regardless of font size)
- Box shadows and text shadows
- Small, fixed-size decorative elements
- When you need pixel-perfect positioning
- Minimum and maximum constraints in
clamp()are oftenpx
When to avoid px:
- Font sizes (breaks user accessibility settings, explained below)
- Spacing that should scale with font size
- Anything that should adapt to user preferences
Why px Breaks Accessibility
This is the most important reason to use relative units for font sizes. Browsers let users set their preferred default font size. The default is 16px, but users with low vision might set it to 20px or 24px. When you write font-size: 16px, you override that preference. Your text stays at 16px regardless of what the user has set.
When you write font-size: 1rem, your text respects the user's preference. If they set their browser to 20px, your 1rem becomes 20px. Your layout adapts to them rather than forcing them to adapt to you.
This is not just a nice-to-have. WCAG accessibility guidelines require that text can be resized up to 200% without loss of content or functionality. Using absolute px for font sizes makes it much harder to meet that requirement.
---
Relative Units
Relative units derive their size from something else: a parent element, the root element, or the viewport. This is where the flexibility of CSS comes from.
rem (Root Em)
rem stands for "root em" and is relative to the font size of the root element (<html>). By default, browsers set the root font size to 16px, so 1rem = 16px by default.
html {
font-size: 16px; /* default, this is what browsers use */
}
h1 {
font-size: 2rem; /* 32px at default */
margin-bottom: 1.5rem; /* 24px at default */
}
h2 {
font-size: 1.5rem; /* 24px at default */
margin-bottom: 1rem; /* 16px at default */
}
p {
font-size: 1rem; /* 16px at default */
line-height: 1.5; /* Note: line-height with no unit is a multiplier, which is preferred */
}
.small-print {
font-size: 0.875rem; /* 14px at default */
}Why rem is the best choice for font sizes:
Since rem is always relative to the root, it does not compound (unlike em, explained below). The calculation is always predictable: multiply the rem value by the root font size. And crucially, if the user changes their browser's default font size, all your rem values scale proportionally.
/* Good: respects user preferences, scales consistently */
.body-text {
font-size: 1rem;
margin-bottom: 1rem;
}
/* Bad: ignores user font size preference */
.body-text {
font-size: 16px;
margin-bottom: 16px;
}The 62.5% base font size trick:
Many developers set the root font size to 62.5% to make the mental math easier:
html {
font-size: 62.5%; /* Makes 1rem = 10px */
}
body {
font-size: 1.6rem; /* 16px */
}
h1 {
font-size: 3.2rem; /* 32px */
margin-bottom: 2.4rem; /* 24px */
}With this approach, you think in pixels (familiar) but write in rem (accessible and scalable). The trade-off: you must remember to set body font-size back to 1.6rem, because 62.5% makes the default body text 10px, which is too small.
This technique is valid but somewhat controversial. The alternative is to just learn to work with 16px as your base: 1.5rem = 24px, 2rem = 32px, 0.875rem = 14px. After a while, these conversions become automatic.
rem for spacing:
rem is also excellent for margins and padding, particularly for spacing between elements at the layout level. Since these values are all relative to the same root, your vertical rhythm stays consistent:
.section {
padding: 4rem 2rem; /* 64px / 32px at default */
margin-bottom: 6rem; /* 96px at default */
}
.card {
padding: 1.5rem; /* 24px at default */
gap: 1rem; /* 16px at default */
}em (Element Em)
em is relative to the font size of the element itself (for font-size property) or the computed font size of the current element (for other properties). This makes it useful for components that should scale proportionally with their own text size.
.card {
font-size: 1rem; /* 16px */
padding: 1.5em; /* 24px - 1.5 × 16px */
gap: 0.75em; /* 12px - 0.75 × 16px */
}
.card--large {
font-size: 1.25rem; /* 20px */
padding: 1.5em; /* 30px - 1.5 × 20px, scaled up automatically */
gap: 0.75em; /* 15px - scaled up automatically */
}Notice how .card--large gets larger padding and gap automatically because em is relative to the element's own font size. You changed one property (font-size) and everything else scaled with it. This is the power of em for component-internal spacing.
When to use em:
- Padding and margins within a component that should scale with its font size
- Icon sizing next to text (an icon at
1emmatches the text height) - Letter spacing and word spacing (these scale naturally with text)
- Media query breakpoints (more on this below)
The em compounding problem:
The notorious downside of em is that it compounds when nested. If a parent has font-size: 1.2em and a child also has font-size: 1.2em, the child's computed size is 1.44em of the root:
.parent {
font-size: 1.2em; /* 19.2px (if root is 16px) */
}
.parent .child {
font-size: 1.2em; /* 23.04px - 1.2 × 19.2px, not 1.2 × 16px */
}
.parent .child .grandchild {
font-size: 1.2em; /* 27.648px - compounding continues */
}This is exactly why rem is preferred for font sizes: it always references the root and never compounds. Use em for spacing within a component (padding, margin, gap) where you want scaling, but use rem for font sizes where predictability matters.
em for media query breakpoints:
There is a strong argument for writing media queries in em rather than px. When a user zooms in or changes their font size, em-based media queries respond to the effective layout width rather than the raw pixel width. This means your breakpoints trigger at the right visual moment regardless of zoom level.
/* em-based breakpoints respond correctly to user zoom */
@media (min-width: 48em) { /* ~768px */
.container { max-width: 48rem; }
}
@media (min-width: 64em) { /* ~1024px */
.container { max-width: 60rem; }
}
/* px-based breakpoints ignore zoom */
@media (min-width: 768px) { ... }---
Viewport Units
Viewport units are relative to the size of the browser viewport: the visible area of the page inside the browser window, excluding toolbars and browser chrome.
vw and vh (Viewport Width and Viewport Height)
1vw = 1% of the viewport width
1vh = 1% of the viewport height
.hero {
height: 100vh; /* full viewport height */
width: 100%; /* full width (percentage of parent, not vw in this case) */
}
.fluid-padding {
padding: 5vw; /* padding scales with viewport width */
}
.fluid-text {
font-size: 4vw; /* scales linearly with viewport width */
}When to use vw:
- Side padding that scales smoothly with viewport width
- Decorative elements sized as a proportion of the screen
- Fluid typography (combined with
clamp(), discussed below)
When to use vh:
- Full-screen hero sections
- Sticky sidebars that fill the viewport height
- Modal overlays
The mobile vh problem:
On mobile browsers, 100vh can be taller than what is actually visible on screen. Mobile browsers include the address bar and navigation bar in the viewport calculation, but those elements hide and show as you scroll, changing what is actually visible. A 100vh element can be cut off by the address bar.
The newer dynamic viewport units solve this:
/* svh: small viewport height - always excludes the browser UI */
.hero {
height: 100svh;
}
/* lvh: large viewport height - always includes the browser UI */
.scroll-container {
min-height: 100lvh;
}
/* dvh: dynamic viewport height - adapts as browser UI shows/hides */
.hero {
height: 100dvh; /* the one you usually want */
}dvh is supported in all modern browsers and is the recommended replacement for 100vh in hero sections.
vmin and vmax
Less common but occasionally useful:
vmin: The smaller ofvwandvhvmax: The larger ofvwandvh
/* Square that fits within the viewport regardless of orientation */
.full-screen-square {
width: 80vmin;
height: 80vmin;
}---
Percentage
Percentages are relative to the parent element's corresponding dimension. They are fundamental for fluid layouts.
.container {
max-width: 1200px;
width: 90%; /* 90% of the parent, up to 1200px */
margin: 0 auto; /* center it */
}
.two-column-layout {
display: flex;
}
.sidebar {
width: 30%; /* 30% of the flex container */
}
.main-content {
width: 70%; /* remaining 70% */
}Percentage for height is tricky:
height: 50% only works if the parent has a definite height. If the parent's height is determined by its content (the default), the percentage resolves to auto and does nothing useful. This is a common source of confusion:
/* This works: parent has a fixed height */
.parent {
height: 400px;
}
.child {
height: 50%; /* 200px */
}
/* This does NOT work as expected: parent has no definite height */
.parent {
/* height determined by content */
}
.child {
height: 50%; /* resolves to auto, has no effect */
}For making a child fill its parent's height without a fixed height, use flexbox or grid instead.
---
The Modern Approach: clamp(), min(), and max()
These CSS functions combine units and values to create adaptive, responsive styles without media queries.
clamp()
clamp(minimum, preferred, maximum) - sets a value that scales with the viewport but never goes below the minimum or above the maximum.
/* Font that scales between 1.25rem and 3rem based on viewport width */
h1 {
font-size: clamp(1.25rem, 5vw, 3rem);
}
/* This is equivalent to:
- If viewport is very narrow: font-size = 1.25rem
- If viewport is wide: font-size = 5vw
- If viewport is very wide: font-size = 3rem
- In between: font-size = 5vw (scales smoothly)
*/
/* Container padding that never gets too cramped or too generous */
.section {
padding: clamp(1rem, 4vw, 4rem);
}
/* Fluid heading with minimum and maximum bounds */
h2 {
font-size: clamp(1rem, 2.5vw + 0.5rem, 2.5rem);
}The last example 2.5vw + 0.5rem is a linear interpolation: it scales with the viewport width but also has a rem component. This technique, sometimes called "fluid type" or "CSS fluid typography," eliminates the need for breakpoint-based font size adjustments entirely.
min() and max()
min() returns the smallest of the values. max() returns the largest.
/* Width that is either 90% of parent or 800px, whichever is smaller */
.container {
width: min(90%, 800px);
}
/* Minimum font size that scales but never gets smaller than 1rem */
.responsive-text {
font-size: max(1rem, 2.5vw);
}
/* Gap that is at least 1rem but prefers 3% of width */
.grid {
gap: max(1rem, 3%);
}These are more concise alternatives to clamp() when you only need a one-sided constraint.
---
A Complete Practical Example
Here is a real-world component demonstrating good unit choices throughout:
/* Root: let users override with browser preferences */
html {
font-size: 100%; /* equivalent to browser default of 16px */
}
/* Layout container */
.page-wrapper {
max-width: 75rem; /* 1200px - max width in rem */
width: min(90%, 75rem); /* fluid but capped */
margin: 0 auto;
padding: 0 clamp(1rem, 5vw, 3rem); /* fluid horizontal padding */
}
/* Hero section */
.hero {
min-height: 100dvh; /* full viewport, mobile-safe */
display: flex;
align-items: center;
padding: clamp(2rem, 8vw, 6rem) 0; /* fluid vertical padding */
}
/* Hero heading */
.hero__title {
font-size: clamp(2rem, 5vw + 1rem, 5rem); /* fluid, bounded */
line-height: 1.1; /* unitless: relative to element font-size */
margin-bottom: 0.5em; /* em: scales with this heading's font size */
letter-spacing: -0.02em; /* em: stays proportional to text */
}
/* Body text */
.body-text {
font-size: 1rem; /* rem: respects user preferences */
line-height: 1.6; /* unitless multiplier */
max-width: 65ch; /* ch: relative to character width - great for readability */
margin-bottom: 1rem; /* rem: consistent vertical spacing */
}
/* Card component */
.card {
font-size: 1rem; /* rem: predictable base */
padding: 1.5em; /* em: scales if card font-size changes */
border: 1px solid #ddd; /* px: always 1px regardless of font size */
border-radius: 0.5rem; /* rem: consistent across all cards */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* px: fixed shadow */
}
/* Card variant with larger text */
.card--featured {
font-size: 1.125rem; /* rem: slightly larger */
/* padding and gap will scale automatically because they use em */
}
/* Media query in em */
@media (min-width: 48em) { /* ~768px, zoom-aware */
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 64em) { /* ~1024px */
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}---
The ch Unit for Typography
ch is often overlooked but extremely useful for text containers. It represents the width of the "0" character in the current font. Since "0" is roughly average character width, ch gives you an approximation of character count:
/* Optimal line length for readability is 60-75 characters */
.article-body {
max-width: 70ch; /* roughly 70 characters per line */
margin: 0 auto;
}
/* Form fields sized to their content */
.date-input {
width: 12ch; /* fits "MM/DD/YYYY" comfortably */
}
.phone-input {
width: 16ch; /* fits "+1 (555) 123-4567" */
}Typography researchers have consistently found that line lengths of 60 to 75 characters optimize reading speed and comprehension. max-width: 65ch is one of the most useful single lines of CSS for text-heavy content.
---
The ex and lh Units
Two less common units worth knowing:
ex: Height of the lowercase "x" in the current font. Roughly equivalent to 0.5em in most fonts. Useful for very precise vertical alignment of text elements.
lh: Equal to the line-height of the element. Available in modern browsers. Useful for positioning elements relative to line height:
.icon-inline {
height: 1lh; /* same height as one line of text */
vertical-align: middle;
}---
Unit Decision Framework
Use this as a mental checklist:
| What You Are Styling | Recommended Unit | Reason |
|---|---|---|
| Body font size | rem | Respects user preferences, predictable |
| Heading font sizes | rem or clamp() | Consistent scale, optionally fluid |
| Component padding | em | Scales with component font size |
| Layout margins | rem | Consistent vertical rhythm |
| Borders | px | Always 1px regardless of context |
| Box shadows | px | Fixed, decorative |
| Container max-width | px or rem | Fixed maximum, not responsive |
| Container width | % or min() | Fluid |
| Hero section height | 100dvh | Mobile viewport safe |
| Full-screen overlays | 100vw / 100vh | Covers viewport |
| Text container width | ch | Character-based, readability-optimized |
| Media queries | em | Zoom-aware |
| Fluid typography | clamp(rem, vw, rem) | Responsive with bounds |
| Icon size next to text | em or 1em | Matches adjacent text |
| Grid/flex gaps | rem or em | Consistent or proportional |
| Letter spacing | em | Proportional to font size |
| Line height | Unitless | Relative multiplier, no inheritance issues |
---
Why Unitless Line Height Matters
A subtle but important point: you should almost always set line-height without a unit:
/* Problematic: inherits a computed value, not a ratio */
p { line-height: 1.5em; } /* Inherits 24px, not 1.5 × child font-size */
/* Correct: inherits the ratio, computes correctly for each element */
p { line-height: 1.5; } /* Each element gets 1.5 × its own font-size */When line-height has a unit (em, rem, px), the computed pixel value is inherited by children. When it is unitless, the number itself is inherited, and each child computes its own line height based on its own font size. Unitless line-height is almost always what you want.
---
Converting Between Units
When refactoring a codebase from px to rem, or when you need to quickly verify what a CSS value resolves to, doing the math manually gets tedious. The CSS Units Converter on ToolBox lets you:
- Convert between px, rem, em, vw, vh, ch, and percentage in real time
- Set your root font size and viewport dimensions for accurate results
- See all conversions update as you type
This is particularly useful when you are working on a codebase you did not write and need to understand what the existing px values translate to in rem.
---
Practical Refactoring: px to rem
If you are inheriting a codebase that uses px everywhere for fonts and spacing, here is a methodical approach to refactoring:
/* Step 1: Confirm the root font size is 16px (default or explicit) */
html {
font-size: 100%; /* keeps browser default, which is 16px */
}
/* Step 2: Convert font sizes */
/* Old: */
h1 { font-size: 32px; }
h2 { font-size: 24px; }
p { font-size: 16px; }
/* New: divide by 16 to get rem */
h1 { font-size: 2rem; } /* 32 / 16 = 2 */
h2 { font-size: 1.5rem; } /* 24 / 16 = 1.5 */
p { font-size: 1rem; } /* 16 / 16 = 1 */
/* Step 3: Convert major spacing */
/* Old: */
.section { padding: 48px 32px; margin-bottom: 64px; }
/* New: */
.section { padding: 3rem 2rem; margin-bottom: 4rem; }
/* Step 4: Keep px for borders, shadows, and fine-grained details */
.card { border: 1px solid #ccc; }
.card { box-shadow: 0 2px 4px rgba(0,0,0,0.1); }Use the CSS Units Converter to quickly calculate the rem equivalents during a refactoring session.
---
The Full Unit Reference
| Unit | Type | Relative To | Good For |
|---|---|---|---|
| px | Absolute | Nothing (fixed) | Borders, shadows, fine details |
| rem | Relative | Root font size | Fonts, layout spacing |
| em | Relative | Current element font size | Component padding, icons, media queries |
| % | Relative | Parent element | Widths, flex/grid context |
| vw | Viewport | Viewport width | Fluid spacing, typography |
| vh | Viewport | Viewport height | Full-screen sections |
| dvh | Viewport | Dynamic viewport height | Mobile-safe hero sections |
| svh | Viewport | Small viewport height | Smallest stable viewport |
| lvh | Viewport | Large viewport height | Largest possible viewport |
| vmin | Viewport | Smaller of vw/vh | Squares that fit viewport |
| vmax | Viewport | Larger of vw/vh | Rare, full-coverage uses |
| ch | Typography | Width of "0" character | Text container width |
| ex | Typography | Height of "x" character | Vertical text alignment |
| lh | Line height | Line height of element | Inline element sizing |
---
Wrapping Up
The rule of thumb that covers 90% of situations:
- Borders and shadows: always
px - Font sizes: always
rem, sometimesclamp(rem, vw, rem)for fluid scaling - Component padding:
emso it scales with the component - Layout spacing:
remfor consistency - Full-screen sections:
100dvh - Text containers:
chfor optimal line length - Media queries:
emfor zoom-awareness - Widths:
%ormin()for fluid,max-widthinremorpx
The CSS Units Converter on ToolBox is the fastest way to check your work when refactoring or building from scratch. Set your viewport dimensions, enter a value, and instantly see the equivalent in every unit. No signup, no tracking, runs entirely in your browser.
Related Tools
Free, private, no signup required
CSS Flexbox Generator
Flexbox generator - visual CSS flexbox layout builder with live preview and ready-to-copy CSS code
CSS Grid Generator
Free online CSS grid generator - visual CSS grid layout builder with live preview and code export
CSS Animation Generator
Free online CSS animation generator - generate CSS keyframe animations visually
CSS Formatter
Free online CSS formatter - format and beautify CSS code with configurable options
You might also like
Want higher limits, batch processing, and AI tools?