AI Tools Free
Developer & Code Tutorials

PX vs. REM vs. EM in Modern CSS: Complete Conversion Formulas and Fluid clamp() Typography

Convert PX to REM and EM in modern CSS, build fluid typography scales with the clamp() linear interpolation formula, and align text to a baseline grid.

Interactive tool PX to REM Converter
PX to REM Converter Open Free Tool

Hardcoding font-size: 16px across a stylesheet looks harmless on a developer's 27-inch monitor. The problem shows up when a user with low vision sets their browser default font size from 16px to 24px in Chrome or Firefox settings: every px-locked heading and paragraph ignores their preference completely, while rem-based text scales cleanly on the first render.

That doesn't mean you should convert every single number in your CSS file to rem. Using rem for a 1px card border or using em for nested container widths creates a different mess. Each unit has a specific job in a design system.

Quick Decision Matrix: When to Use px, rem, em, or vw/vh

CSS UnitRelative ToScales with User Browser Font Preference?Best Production Use CaseAvoid Using For
remRoot <html> font-size (default 16px)YesGlobal font sizes, layout max-width containers, section vertical spacing, media query breakpoints1px borders or inline icons that must match a specific parent's font size
emCurrent element's (or parent's) computed font-sizeYes (compounds when nested)Button padding-inline: 1.1em, badge spacing, inline SVG icon sizing (width: 1em), letter-spacingMulti-level nested list or grid font sizes (causes compounding shrinkage)
pxCSS reference pixel (1/96th of an inch)No (scales only on full-page zoom)1px or 2px borders, outline-offset, box-shadow blur, hairline dividersBody copy, headings, or fixed-height text containers
vw / svh / dvhViewport width or small/dynamic viewport heightNoFull-bleed hero heights (min-height: 100svh) and the fluid slope term inside clamp()Standalone font-size: 3vw without a rem base

The Exact PX to REM Formula (And Why Compounding Breaks em)

In every modern browser, the unstyled root <html> element defaults to 16px. Converting any pixel value from a Figma spec into rem is a single division:

PX to REM Formula: rem = target_px / base_font_size_px (at default 16px root: rem = px / 16, and px = rem * 16).

Common typographic scale steps at a 16px root:

  • 12px = 0.75rem
  • 14px = 0.875rem
  • 16px = 1rem
  • 18px = 1.125rem
  • 20px = 1.25rem
  • 24px = 1.5rem
  • 32px = 2rem
  • 44px = 2.75rem

Why not use em for typography? Because em resolves against the parent element's font size. If you set .card { font-size: 0.875em } (14px) and put another .card or .meta with 0.875em inside it, the inner text computes to 16 * 0.875 * 0.875 = 12.25px. Add a third nesting level and your text shrinks to 10.7px. Reserve em for properties that *should* scale proportionally with the local text size of a single component, such as button padding (padding: 0.6em 1.2em).

How to Calculate Fluid Typography with clamp(min, preferred, max)

Instead of writing three @media breakpoints to step an <h1> from 28px (1.75rem) on a 375px mobile screen up to 44px (2.75rem) on a 1200px desktop viewport, CSS clamp() interpolates smoothly between the two bounds.

Linear Interpolation Formula for clamp(): slope = (max_font_px - min_font_px) / (max_viewport_px - min_viewport_px), y_intercept_px = min_font_px - (slope * min_viewport_px), and preferred = (y_intercept_px / 16)rem + (slope * 100)vw.

Let's compute the exact clamp() declaration for a heading that scales from 28px at 375px viewport width to 44px at 1200px viewport width:

/*
  1. Slope = (44 - 28) / (1200 - 375) = 16 / 825 = 0.019394 (1.94vw)
  2. Intercept = 28 - (0.019394 * 375) = 28 - 7.2727 = 20.7273px (1.295rem)
*/
h1 {
  font-size: clamp(1.75rem, 1.295rem + 1.94vw, 2.75rem);
  line-height: 1.18;
  letter-spacing: -0.03em;
}

Notice that the middle preferred argument is 1.295rem + 1.94vw, not a bare vw number. Keeping the 1.295rem intercept inside the preferred expression guarantees that the heading still respects user font-size zooming under WCAG 1.4.4.

Aligning Line-Height and Spacing to a Vertical Baseline Grid

When you convert font sizes to rem, pair them with unitless line-height values or rem multiples that snap to a 4px (0.25rem) or 8px (0.5rem) vertical rhythm. For example, 16px (1rem) body text with a 1.75 line-height produces a 28px (1.75rem) line box, which divides cleanly into a 4px sub-grid.

Once your design tokens and fluid clamp() rules are finalized, strip comments and whitespace from production stylesheet bundles before deployment.

Frequently asked questions

Should I still use the html { font-size: 62.5%; } trick so 1rem equals 10px?

In greenfield projects with no third-party UI libraries, 62.5% works if you immediately set body { font-size: 1.6rem; }. However, in modern component apps that import third-party packages, Tailwind, or Radix primitives expecting 1rem = 16px, setting html to 62.5% shrinks every external modal, tooltip, and control to 62.5% of its intended size. Keep the root at 100% (16px) and use CSS custom properties or a PX-to-REM converter instead.

Why does using pure vw inside clamp() fail WCAG 1.4.4 text zoom?

Viewport units (vw) only respond to browser window width, not browser zoom level or user font-size preferences. Writing clamp(1.5rem, 4vw, 3rem) locks the middle term to viewport width. Always combine rem and vw in the preferred value, such as clamp(1.5rem, 1rem + 2.5vw, 3rem), so zooming to 200% scales the text properly.

When is px actually better than rem in CSS?

Use px for hairlines, borders (1px or 2px), subtle box-shadow offsets, and small decorative radii that should remain crisp on physical screen pixels rather than ballooning when a user increases their reading font size.