nicetool.dev logo
#css#accessibility#frontend

px vs rem in CSS: When Each Unit Is the Right Choice

rem respects the user's font size setting, px does not. Learn what really changes between the two units, why zoom is not the whole story, and a practical rule for fonts, spacing, borders and media queries.

5 min read

Every frontend developer has seen the advice "use rem, not px". Fewer people can explain what actually breaks when you use pixels. People often reply that "browser zoom works fine with px". That is true, and it is why the debate keeps going. This article shows the exact case where the two units behave differently, then gives a simple rule you can apply in any codebase.

What the units mean

  • px is the CSS pixel. It is an absolute unit inside CSS: 16px is always 16 CSS pixels, whatever the user's settings. (A CSS pixel is not a physical screen pixel. A phone with a device pixel ratio of 3 draws each CSS pixel with 3×3 physical pixels.)
  • rem means "root em": a multiple of the font-size of the root <html> element. If you do not set that font size, it comes from the browser, which defaults to 16px.
  • em is a multiple of the font size of the current element (or of the parent, when used in font-size itself).

So with default settings 1rem = 16px, 0.875rem = 14px and 1.5rem = 24px. The conversion is a simple division:

rem = px / root font size

Zoom vs font size: the case that matters

Browsers give users two different ways to make text bigger.

1. Page zoom (Ctrl/⌘ and +). Zoom enlarges everything: px, rem, images, borders. In this case px and rem behave the same. This is why many developers believe the units are equivalent.

2. Default font size setting. In Chrome this is Settings → Appearance → Font size, and Firefox, Safari and Edge have similar options. Mobile browsers and operating systems have their own text size settings too. Users with low vision often prefer this option to zoom, because it makes text bigger without making the whole layout huge and forcing horizontal scrolling.

This setting changes the browser's default font size, for example from 16px to 20px. Anything sized in rem grows by 25%. Anything sized in px stays the same.

If your CSS says:

html { font-size: 16px; }   /* overrides the user's choice */
body { font-size: 14px; }   /* ignores it as well */

then a user who asked for larger text gets exactly the same small text. You have quietly disabled an accessibility feature. With:

/* no font-size on html: keep the browser default */
body { font-size: 1rem; }

the text follows the user's preference.

WCAG success criterion 1.4.4 (Resize Text) requires that text can be enlarged to 200% without losing content. Page zoom usually satisfies it on its own. Respecting the font size preference is about doing the right thing for the users who rely on that setting, not only about passing an audit.

What to size with rem

Font sizes: always rem

This is the most important part. Use rem for font-size everywhere, including small text such as captions and badges. Use unitless values for line-height (line-height: 1.5) so it scales with whatever font size is in effect.

Spacing: usually rem

If the text grows but the padding around it stays at fixed pixels, buttons and cards start to feel cramped. Sizing padding, margins and gaps in rem keeps proportions when the user's font size changes. This is what Tailwind CSS does. Its spacing scale is defined in rem, so p-4 is 1rem and text-sm is 0.875rem.

Media queries: rem or em

Inside a media query, rem and em always refer to the browser's initial font size, not to the font-size you set on html. That is useful: @media (min-width: 48rem) is 768px by default, but for a user with a 20px default it becomes 960px. A user with large text therefore gets the more spacious mobile layout earlier, instead of a desktop layout with text squeezed into narrow columns.

Fluid typography: mix rem with vw

font-size: 4vw does not grow with zoom, because zooming changes the viewport width in CSS pixels. Always include a rem term:

h1 { font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem); }

What to keep in px

Some things should not scale with text size:

  • Borders and hairlines. 1px borders should stay thin. A 0.0625rem border becomes a blurry 1.25px line at a 20px default.
  • Box shadows and outlines that are purely visual.
  • Fixed-size media such as an avatar image whose file is exactly 64×64.
  • Small offsets used for optical alignment, like nudging an icon by 1px.

A useful mental test is to ask: if a user doubles their text size, should this grow too? If yes, use rem. If it is decoration, px is fine.

The 62.5% trick: convenient but risky

A popular pattern makes the math easier:

html { font-size: 62.5%; }  /* 10px at the default 16px */
body { font-size: 1.6rem; } /* 16px */

Now 1.4rem = 14px, which is easy to calculate mentally. Because it uses a percentage, it still respects the user's preference. The downsides are practical:

  • Every third-party component that assumes 1rem = 16px (date pickers, embedded widgets, UI libraries) suddenly renders at 62.5% of its intended size.
  • You must remember to reset the font size on body, otherwise unstyled text is 10px.
  • Utility frameworks like Tailwind expect the default root size.

Today the trick is rarely worth it. Editors and tools can do the conversion for you. Keep the root at its default and use a converter or a few memorized values instead.

Values worth memorizing

px rem (base 16)
12 0.75
14 0.875
16 1
18 1.125
20 1.25
24 1.5
32 2
48 3

For anything else, the px to rem converter converts in both directions, supports a custom base size and can convert a whole block of CSS at once. It keeps 1px borders as they are if you want, which is handy when migrating an old stylesheet.

Migrating an existing codebase

You do not need a big-bang rewrite:

  1. Remove any font-size in px on html or body. This single change makes rem-based components respect user settings immediately.
  2. Convert font sizes first, file by file. They have the biggest accessibility impact.
  3. Convert spacing next, starting with shared components such as buttons, inputs and cards.
  4. Leave borders and shadows in px.
  5. Test with a 20px or 24px default font size in your browser settings, not only with zoom. Look for text that overflows fixed-height containers, which is the most common bug after migration.

Summary

  • px and rem look identical with default settings and with page zoom.
  • They differ when the user changes the default font size: rem follows it, px ignores it.
  • Use rem for font sizes, spacing and media queries, px for borders and decorative details, and unitless line-height.
  • Never set the root font size in px.

The extra effort is small, and for the users who depend on larger text it decides whether your site is usable at all.

Written by

Nguyễn Thành Nam

Full-Stack Engineer · Backend & Platform

Full-stack engineer with 9+ years of experience building high-traffic APIs and platforms with Go, NestJS, PHP (Laravel, Phalcon) and Node.js. Specializes in GraphQL API design, real-time systems and performance optimization, and builds NICETOOL.dev for the developer community.

Try it yourself

Related articles