DEV Community

Muhammad Awais
Muhammad Awais

Posted on • Originally published at webtoolshub.online

Stop Doing px rem Math in Your Head (Here's Why It's Killing Your Flow)

You're 40 minutes deep into a component. Figma says font-size: 24px. You open a calculator, type 24 / 16, get 1.5, switch back to VS Code, type 1.5rem, close the calculator.

Three minutes later you need 36px. Calculator again. 36 / 16... is that 2.25? Let me double check...

Sound familiar? I've been there more times than I can count. And honestly — it's one of those tiny frictions that doesn't feel like a big deal until you realize you've done it 20 times in a single session.


Why We're Still Doing This Manually

The formula is stupid simple:

rem = px ÷ base font size
px  = rem × base font size
Enter fullscreen mode Exit fullscreen mode

With a 16px base (browser default):

24px ÷ 16 = 1.5rem   ✓
36px ÷ 16 = 2.25rem  ✓
14px ÷ 16 = 0.875rem ✓
Enter fullscreen mode Exit fullscreen mode

So yeah, the math isn't hard. The problem is context switching. Every time you leave your editor to calculate something, you're paying a mental overhead cost. Stack that 15–20 times per session and you've lost a solid chunk of deep work time.


The Quick Reference You Actually Need

Here are the values I reach for constantly — with a standard 16px base:

px rem
10px 0.625rem
12px 0.75rem
14px 0.875rem
16px 1rem
18px 1.125rem
20px 1.25rem
24px 1.5rem
28px 1.75rem
32px 2rem
36px 2.25rem
40px 2.5rem
48px 3rem
56px 3.5rem
64px 4rem
80px 5rem
96px 6rem

Bookmark this section. Seriously.


Wait — Why rem At All?

If you're newer to CSS, you might be wondering why we bother with rem in the first place. Pixels work fine, right?

Here's the thing: px is an absolute unit. When you write font-size: 16px, that's always 16 pixels, regardless of what the user has set in their browser preferences.

rem is relative to the root <html> element's font size — which browsers default to 16px, but users can change. That one difference matters a lot for accessibility.

/* User sets browser font to "Large" (say, 20px) */

p { font-size: 16px; }   /* still renders at 16px — ignores user */
p { font-size: 1rem; }   /* renders at 20px — respects user */
Enter fullscreen mode Exit fullscreen mode

WCAG 2.2 criterion 1.4.4 (Resize Text) requires that users can resize text up to 200% without breaking layout or losing content. rem handles this automatically. px doesn't.

Beyond accessibility, rem makes your spacing system predictable. Change the root font size once — everything scales proportionally.


The html { font-size: 62.5% } Trick (And Why It's Outdated)

You've probably seen this pattern in older codebases:

html {
  font-size: 62.5%; /* Makes 1rem = 10px */
}

h1 { font-size: 3.6rem; }  /* = 36px */
p  { font-size: 1.6rem; }  /* = 16px */
Enter fullscreen mode Exit fullscreen mode

The appeal: round numbers. 36px = 3.6rem feels cleaner than 2.25rem.

The problem: setting font-size: 62.5% on html overrides the user's browser preference before anything can respond to it. You're essentially opting out of accessibility at the root level.

Modern best practice is to leave the html font size alone (or use 100%) and let 1rem = user's preference. Then use a converter tool to do the math — not a base-size hack.


Dealing With a Non-Standard Base in Your Project

Not every project uses 16px. You might inherit a codebase with:

html { font-size: 10px; }
/* or */
html { font-size: 18px; }
Enter fullscreen mode Exit fullscreen mode

In these cases the standard conversion tables are wrong. 24px is NOT 1.5rem if your base is 10px — it's 2.4rem.

This is one reason I always use a proper px → rem converter with a configurable base rather than relying on memory or hardcoded tables. When I'm working on a project with a custom root font size, I just update the base field and every value recalculates correctly.

Free PX to REM Converter — bidirectional, custom base, bulk conversion


My Actual Workflow: Converting a Full Type Scale

When I'm starting a new design system or setting up typography tokens, I use the bulk converter instead of doing values one by one.

Say your designer gave you this type scale in Figma (all in pixels):

12 / 14 / 16 / 18 / 20 / 24 / 30 / 36 / 48 / 60 / 72
Enter fullscreen mode Exit fullscreen mode

Throw all 11 values into the bulk converter, hit "Copy All", and you get:

12px = 0.75rem
14px = 0.875rem
16px = 1rem
18px = 1.125rem
20px = 1.25rem
24px = 1.5rem
30px = 1.875rem
36px = 2.25rem
48px = 3rem
60px = 3.75rem
72px = 4.5rem
Enter fullscreen mode Exit fullscreen mode

Paste directly into your CSS custom properties or Tailwind config. Done in 30 seconds instead of 5 minutes.

:root {
  --text-xs:   0.75rem;   /* 12px */
  --text-sm:   0.875rem;  /* 14px */
  --text-base: 1rem;      /* 16px */
  --text-lg:   1.125rem;  /* 18px */
  --text-xl:   1.25rem;   /* 20px */
  --text-2xl:  1.5rem;    /* 24px */
  --text-3xl:  1.875rem;  /* 30px */
  --text-4xl:  2.25rem;   /* 36px */
  --text-5xl:  3rem;      /* 48px */
}
Enter fullscreen mode Exit fullscreen mode

rem in Media Queries (The Part Nobody Talks About)

Here's something that trips people up: media queries accept rem too — and it's actually the better choice.

/* Works, but doesn't scale with user font preference */
@media (min-width: 768px) { ... }

/* Scales with user preference */
@media (min-width: 48rem) { ... }
Enter fullscreen mode Exit fullscreen mode

With px breakpoints: if a user bumps their font size to 20px, your layout still breaks at 768 device pixels regardless of their preference.

With rem breakpoints: the breakpoint itself scales. At 20px root, 48rem = 960px — your layout adapts. The user's preference propagates all the way through your responsive design.

Quick conversion for the standard breakpoints:

480px  = 30rem
640px  = 40rem
768px  = 48rem
1024px = 64rem
1280px = 80rem
1440px = 90rem
1536px = 96rem
Enter fullscreen mode Exit fullscreen mode

Tailwind Users: Arbitrary Values + rem

Tailwind's preset scale already uses rem internally — text-base is 1rem, text-xl is 1.25rem, and so on. But when a design spec calls for something off the preset scale, you need arbitrary values:

<h1 class="text-[2.25rem] leading-[2.75rem]">Heading</h1>
<div class="mt-[1.875rem] px-[1.5rem]">Content</div>
Enter fullscreen mode Exit fullscreen mode

The converter I mentioned generates the Tailwind arbitrary value string automatically alongside the standard CSS — so you don't have to manually format it.

If you're on Tailwind v4 and building a custom @theme block, those tokens go in as rem values too:

@theme {
  --font-size-display: 3rem;      /* 48px */
  --font-size-heading: 2.25rem;   /* 36px */
  --spacing-section: 5rem;        /* 80px */
}
Enter fullscreen mode Exit fullscreen mode

PostCSS Automation (For Large Codebases)

If you're migrating a legacy codebase with thousands of px declarations, doing it manually isn't realistic. There's a PostCSS plugin — postcss-pxtorem — that converts px to rem at build time.

npm install postcss-pxtorem --save-dev
Enter fullscreen mode Exit fullscreen mode
// postcss.config.js
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 16,
      propList: ['font-size', 'line-height', 'letter-spacing'],
      // Leave margins/paddings in px if you want
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You write font-size: 24px in development, it outputs font-size: 1.5rem in production. Same formula this tool uses — just automated.

Be careful with the propList — don't blindly convert everything to rem. Borders, icon sizes, and elements that should never scale usually want to stay in px.


TL;DR

  • Use rem for font sizes and spacing — it respects user browser preferences and satisfies WCAG
  • Use px for borders, shadows, and things that genuinely shouldn't scale
  • rem = px ÷ base font size (default base = 16px)
  • Don't use the 62.5% html trick on new projects — it's an accessibility anti-pattern
  • rem works in media queries too — and it's better for accessibility
  • For bulk conversions or a non-standard base, use a proper tool

WebToolsHub — PX to REM Converter (free, bidirectional, bulk)


If you use CSS custom properties for design tokens, our CSS Variables & Dark Mode guide covers how to wire up a full token system in Next.js. And if you're migrating to Tailwind from a pixel-heavy codebase, the CSS to Tailwind converter handles the class-mapping layer.


Tags: css webdev frontend beginners

Top comments (0)