CSS Topics
🎨 CSS Design✨Lesson 14 of 16

Units & Math Functions

Master absolute vs relative units (px, rem, em, vw, dvh) and modern CSS math functions: calc(), min(), max(), and clamp().

⏱️~5 min read🟢Beginner⚡Jump to Code Editor

🎯 What You Will Master in This Lesson

1.The Full Unit Spectrum: px, rem, em, %, vw/vh/dvh, ch, vmin/vmax — when to use each.
2.rem vs em — the critical difference: Why confusing the two breaks nested component layouts.
3.calc() Anatomy: Mixing mismatched units, operator rules, and nesting.
4.min() & max(): Single-line replacements for old two-property hacks.
5.clamp() Fluid Typography: True responsive text without a single media query.
6.Mobile Viewport Units: dvh vs svh vs lvh — solving the iOS address bar bug.

📏 The Complete CSS Unit Cheat Sheet

CSS has two families of units: absolute (fixed, never change) and relative (scale based on something else). Knowing which family to use — and which reference point — is one of the highest-leverage CSS skills.

ABSOLUTEFixed — ignore user preferences and parent size

px (Pixels)

One CSS pixel. The most precise absolute unit. Best for: 1px hairline borders, box-shadow offsets, icon sizes. Avoid for: font-size (breaks browser zoom accessibility).

pt, cm, mm, in (Print units)

Physical real-world units meant for print stylesheets. 1in = 96px on screen. Almost never used in web CSS — only in @media print rules.

RELATIVEFlexible — scale based on a reference point

rem (Root EM)Relative to <html> font-size

1rem = 16px (browser default). Respects user browser zoom. Industry standard for all typography and spacing. Consistent across nested components.

emRelative to PARENT element font-size

Compounds in nested elements! 1em inside a 2em parent = 32px. Best for: letter-spacing, line-height, border-radius relative to text size.

% (Percentage)Relative to PARENT element dimension

width: 50% = half of parent container width. For height: %, the parent must have an explicit height set. Most fluid layout use-case.

vw / vhRelative to VIEWPORT (browser window)

1vw = 1% of viewport width. 100vh = full browser window height. Problem: vh ignores mobile address bar.

dvh / svh / lvhDynamic/Small/Large Viewport Height

dvh = dynamic, updates as mobile address bar shows/hides. svh = smallest (bar visible). lvh = largest (bar hidden). Use 100dvh for full-screen mobile layouts.

chRelative to current font's "0" glyph width

1ch ≈ width of the “0” character. Typography research: 45–75 chars/line is optimal. max-width: 65ch on article text = perfect reading comfort on any screen, any font.

⚠️ The #1 Confusing Pair: rem vs em

Both sound similar. Both are “relative EM units.” But they reference completely different things — and confusing them creates compounding (cascading multiplication) bugs that are very hard to debug:

/* rem — always relative to <html> = 16px */

html { font-size: 16px; }

.parent { font-size: 2rem; } /* = 32px */

.child { font-size: 1rem; } /* = 16px ✅ always */

/* em — relative to PARENT element font-size */

html { font-size: 16px; }

.parent { font-size: 2em; } /* = 32px */

.child { font-size: 1em; } /* = 32px! ⚠️ inherits parent */

Rule of thumb: Use rem for font sizes and spacing (always predictable). Use em only for properties that should scale relative to that element's own font size — like letter-spacing: 0.05em or padding: 0.75em on a button.

🏔️ Three Measuring Tools — The Iron Ruler, Tailor's Tape, and Mountain Guardrails

📐

The Iron Ruler (px)

A cold steel 30cm ruler. Never bends, never stretches. If you use it to tailor clothes for a growing child, the seams will burst. In CSS, hardcoding px on font sizes breaks accessibility for users who zoom their browser.

Use px for: borders, shadows, icons. Avoid for: font-size.

📏

The Tailor's Elastic Tape (rem & vw)

It breathes with the body. When a user zooms their browser font from 16px to 24px, all rem measurements expand in harmony. When the browser window widens, vw expands proportionally.

The industry standard for typography and spacing.

🛡️

The Mountain Highway Guardrails (clamp())

On a winding cliff road you want to drive freely, but you need guardrails so you don't fall off. clamp(min, preferred, max) lets font size scale with viewport width while guaranteeing it never becomes illegibly small on mobile or grotesquely huge on 4K.

Fluid design with hard safety limits.

🧮 calc() — Mixing Units That Normally Can't Mix

calc() solves the fundamental CSS problem: you cannot normally subtract px from % because they resolve at different times. calc() defers the math to render time when both values are known.

The 4 Operators — and the Critical Whitespace Rule

+ (addition) & * (multiplication)

Whitespace optional around + technically, but always write spaces for consistency. Whitespace optional around *.

calc(100% + 20px) ✅
- (subtraction) — MANDATORY whitespace!

CSS sees -40px as a negative number literal. Without space, calc(100%-40px) silently fails! This is the #1 calc() bug.

calc(100%-40px) ❌ broken!
calc(100% - 40px) ✅ works
Nesting calc() inside calc()

You can nest multiple calc() calls for complex formulas:

width: calc(calc(100% - 40px) / 3);

But modern CSS allows inline math inside min(), max(), clamp() without wrapping in calc().

Real use-cases

/* sidebar + content */

width: calc(100% - 280px);

/* scrollbar offset */

width: calc(100vw - 17px);

/* stacked nav + content */

height: calc(100dvh - 64px);

↔️ min() and max() — Smart Single-Line Constraints

min(a, b) — picks the smaller value

Replaces the classic max-width + width two-property pattern. The browser evaluates both values and uses whichever is numerically smaller.

/* Old pattern — 2 properties */

.card { max-width: 600px; width: 90%; }

/* Modern — 1 property */

.card { width: min(600px, 90%); }

On desktop: 600px < 90% of 1440px (1296px) → uses 600px. On mobile 390px: 90% of 390px (351px) < 600px → uses 351px. Perfect mobile margins automatically!

max(a, b) — picks the larger value

Guarantees a minimum value. Replaces min-width combos. Great for ensuring touch targets meet the minimum 44px WCAG accessibility requirement.

/* Guaranteed minimum touch target */

.btn { padding: max(12px, 3vw); }

/* Fluid padding with a floor */

.section { padding: max(24px, 8vw); }

On mobile 390px: max(12px, 3% of 390 = 11.7px) → uses 12px. On desktop 1440px: max(12px, 3% of 1440 = 43.2px) → uses 43.2px. Auto-scaling with a safe minimum!

📎 clamp() — Fluid Typography Without Media Queries

clamp(minimum, preferred, maximum) is max(minimum, min(preferred, maximum)) — it clamps a fluid value between a hard lower and upper bound. Think of it as a slider with stops at both ends.

Visualizing clamp(1rem, 4vw, 2.5rem)

1rem (16px)
2.5rem (40px)
Small screen (< 400px)4vw = 16px

Preferred ≤ Minimum → browser uses 1rem (16px) — clamped at bottom.

Medium screen (600px)4vw = 24px

Preferred is between bounds → browser uses 24px — fluid scaling!

Large screen (> 625px)4vw = 40px+

Preferred ≥ Maximum → browser uses 2.5rem (40px) — clamped at top.

/* The fluid typography formula — works from 320px to 4K monitors */

h1 { font-size: clamp(1.5rem, 4vw + 0.5rem, 3rem); }

p { font-size: clamp(1rem, 1.5vw + 0.5rem, 1.25rem); }

.section { padding: clamp(1.5rem, 5vw, 5rem); }

/* The + 0.5rem inside preferred prevents font from hitting 0 on microscopic screens */

📱 Mobile Viewport Units — Solving the iOS Address Bar Bug

100vh seemed perfect for full-screen mobile layouts — until iOS Safari and mobile Chrome made the address bar slide up and down during scrolling. 100vh ignores this, so the bottom 50–80px of your page gets hidden behind the bar.

100vh

Old behavior (broken). Calculated once when page loads. Ignores address bar height changes. Bottom CTA buttons hidden on iOS Safari.

100dvh

Dynamic. Updates in real-time as the address bar expands and collapses during scroll. Use this for full-screen app shells and hero sections.

svh / lvh

Small / Large viewport. svh = height with bar fully visible. lvh = height with bar hidden. Use when you need a stable non-jumping value.

/* Hero section — use dvh for correct mobile full-screen */

.hero { min-height: 100dvh; }

/* Safe fallback for older browsers: */

.hero { min-height: 100vh; min-height: 100dvh; }

💻 Live Fluid Units & clamp() Playground

The card uses min(520px, 92%) for width, clamp() for fluid heading and body text, and calc(100% - 100px) for the search input. Try resizing the preview, changing the clamp values, or adding max-width: 65ch to the paragraph!

Interactive Live Editor
index.html
Click & type to editHTML5
Browser Preview

Line-by-Line Code Breakdown

The key patterns inside the playground — explained in full:

width: min(520px, 92%);

Replaces the classic two-line combo of max-width: 520px; width: 92%;. The browser evaluates both at render time: on a 1440px desktop, 92% = 1324.8px > 520px, so it picks 520px. On a 390px iPhone, 92% = 358.8px < 520px, so it picks 358.8px — leaving 4% breathing room on each side automatically.

font-size: clamp(1.25rem, 4vw + 0.5rem, 2.25rem);

The fluid typography sweet spot. The preferred value is 4vw + 0.5rem — a combination of viewport-relative and root-relative. The + 0.5rem ensures the font has a baseline size even on microscopic screens (like smartwatches at 150px width, where pure 4vw = 6px would be illegible). The clamp() then ensures it never goes below 1.25rem (20px) or above 2.25rem (36px).

width: calc(100% - 100px);

The mandatory spaces around -. This makes the input box fill all horizontal space except the 100px submit button sitting beside it. Without calc(), you couldn't subtract a fixed pixel value from a flexible percentage — they resolve at different render stages. The spaces around the minus sign are not stylistic: without them, CSS reads 100%-100px as invalid syntax and silently ignores the entire rule.

max-width: 65ch;

The typography readability unit. 1ch equals the width of the 0 glyph in the current font. Research shows 45–75 characters per line is the optimal reading comfort zone. Setting max-width: 65ch on body text ensures the line length is ideal on any screen and any font — no media query needed. The value auto-adjusts if the user changes their font size preference.

🧠 Check Your Understanding: CSS Units & Math

Q1Why does the CSS expression `width: calc(100%-40px);` fail with a syntax error, while `width: calc(100% - 40px);` works perfectly?

Q2What does `font-size: clamp(1rem, 2.5vw, 2rem);` do?

Q3Why was the new `100dvh` (Dynamic Viewport Height) unit introduced to replace `100vh` on mobile phones?

🛠️

Practice Challenge: Build a Full Fluid System

Open the live editor above and try these 4 experiments in order:

  1. Fluid padding: Find .fluid-card. Change its padding to clamp(12px, 5vw, 40px);. Watch how the card's breathing room scales smoothly as you resize the preview!
  2. Layout balance: Set .action-btn width to 120px. Then set .main-bar to width: calc(100% - 120px);. The input now fills exactly the remaining space regardless of container width.
  3. Typography guard: Find the <p> tag paragraph. Add max-width: 65ch; to its selector. The paragraph will automatically stop at a comfortable reading width on wide screens — no media query needed!
  4. Bonus — Hero section: Add a new <div class="hero">. Give it min-height: 100dvh; and a dark background. Notice the card no longer gets cut off — the viewport height respects the browser chrome!
💡 Click to view the full solution code

.fluid-card { width: min(520px, 92%); padding: clamp(12px, 5vw, 40px); }

.main-bar { width: calc(100% - 120px); }

.action-btn { width: 120px; }

p { max-width: 65ch; }

.hero { min-height: 100dvh; background: #0f172a; }

Common Pitfalls & Interview Preparation

❌ Pitfall 1: Missing Whitespace in `calc()`

calc(100%-20px) silently breaks in all browsers with no error in the console. The rule is not optional: spaces around + and - are required by the CSS spec, not a style preference. Always: calc(100% - 20px).

❌ Pitfall 2: Using `px` for Font Sizes

Setting font-size: 16px on body or headings overrides the user's browser default zoom preference. Users with low vision who set their browser to 200% zoom expect text to double. Use rem for font sizes — it respects user zoom settings and passes WCAG 1.4.4 accessibility checks.

✅ Best Practice: Math Inside min/max/clamp Without calc()

Modern CSS (Level 4) allows arithmetic expressions directly inside min(), max(), and clamp() without wrapping in calc(). So clamp(16px, 2vw + 10px, 32px) is valid and preferred over the verbose clamp(16px, calc(2vw + 10px), 32px).

✅ Best Practice: Always Provide `100vh` Fallback

dvh is well-supported (Chrome 108+, Safari 15.4+, Firefox 101+) but older browsers don't know it. Always declare the safe fallback first: min-height: 100vh; min-height: 100dvh;. Browsers ignore unknown values, so old browsers use vh, modern ones use dvh.

💬 Interview Q: What is the `ch` unit and why is it beloved in typography?

1ch equals the width of the zero (0) glyph in the current active font. It's a proxy for “one character width” in the current typeface.

Typography research (Baymard Institute, Nielsen Norman) consistently shows that 45–75 characters per line produces the optimal reading comfort. Fewer than 45 = too many eye jumps. More than 75 = hard to track back to line start.

max-width: 65ch on article body text guarantees perfect reading line length regardless of font size, font family, or screen size. The ch value scales automatically when users change font size — something a px-based max-width cannot do.

💬 Interview Q: What is the difference between `svh`, `lvh`, and `dvh`?

All three represent viewport height on mobile but measure different states of the browser chrome:

  • svh (Small Viewport Height): The viewport height when the browser's UI bars (address bar, bottom nav) are fully expanded and visible. This is the minimum visible height — the most conservative.
  • lvh (Large Viewport Height): The viewport height when the browser's UI bars are fully retracted/hidden (after scrolling down). This is the maximum possible height.
  • dvh (Dynamic Viewport Height): Updates in real-time as the user scrolls and the bars show/hide. Most accurate, but can cause layout reflows if used on animated elements. Best for hero sections.

Practical advice: Use 100dvh for hero/app shells. Use svh when you want guaranteed no-clipping at worst case.

💬 Interview Q: What's the difference between `rem` and `em`, and when would you use `em`?

rem (Root EM) is always relative to the <html> element's font-size (default 16px). It's consistent everywhere and doesn't compound in nested elements.

em is relative to the current element's own font-size (or inherited font-size from parent). In nested elements, em values multiply: a 2em parent with a 1.5em child = 3em total size relative to root!

When to use em: Properties that should scale proportionally to the element's own text size — like letter-spacing: 0.05em, line-height: 1.6 (unitless preferred), or padding: 0.75em 1.25em on buttons where padding should grow/shrink with the button's font size.

💬 Interview Q: How would you create a fluid type scale without media queries?

Use clamp() with a viewport-relative preferred value mixed with a root-relative base. The formula clamp(min, preferred-vw + base-rem, max) scales smoothly across all screen sizes:

/* Complete fluid type scale — no media queries needed */

:root {

--fs-sm: clamp(0.875rem, 1vw + 0.5rem, 1rem);

--fs-base: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);

--fs-lg: clamp(1.25rem, 2vw + 0.5rem, 1.75rem);

--fs-xl: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);

--fs-2xl: clamp(2rem, 4vw + 0.5rem, 3.5rem);

}

This approach eliminates brittle breakpoint-based font-size overrides and produces text that looks correct on every screen size, including sizes in between standard breakpoints.