CSS3 & Responsive Design

Units & Values: px, em, rem, %, vw, vh

45 min Lesson 18 of 60

Why Units Matter in CSS

Every size, spacing, and dimension in CSS requires a unit. Choosing the right unit is not just a matter of preference -- it directly affects how your design responds to different screen sizes, user preferences, and accessibility settings. A layout built entirely with pixels will break when a user zooms their browser or changes their default font size. A layout built with the right mix of relative units will adapt gracefully to any environment.

CSS offers a wide variety of units, each designed for specific use cases. Understanding when to use each unit is one of the most important skills a CSS developer can master. In this lesson, we will explore every CSS unit in depth, compare them side by side, and establish best practices for building responsive, accessible designs.

Absolute Units

Absolute units have a fixed size that does not change based on any other element or setting. They are called "absolute" because they correspond to real-world measurements (with one exception: the pixel).

  • px (pixel) -- The most common absolute unit. In CSS, a pixel is not necessarily a physical screen pixel. It is a "reference pixel" defined as 1/96th of an inch. On high-DPI (Retina) screens, one CSS pixel may correspond to 2 or more physical pixels. Despite being absolute, pixels scale with browser zoom.
  • cm (centimeter) -- One centimeter. Rarely used in web design but useful for print stylesheets.
  • mm (millimeter) -- One millimeter. Like cm, primarily used for print.
  • in (inch) -- One inch, equal to 96px or 2.54cm. Used for print stylesheets.
  • pt (point) -- One point, equal to 1/72nd of an inch. Traditional typographic unit, common in print but rarely used on the web.
  • pc (pica) -- One pica, equal to 12 points or 1/6th of an inch. Another traditional print unit.

Example: Absolute Units in Practice

/* px -- the workhorse absolute unit */
.border-example {
    border: 1px solid #333;
    border-radius: 4px;
    padding: 16px;
}

/* Physical units for print stylesheets */
@media print {
    .page {
        width: 21cm;       /* A4 width */
        height: 29.7cm;    /* A4 height */
        margin: 2cm;
        font-size: 12pt;   /* Standard print font size */
    }

    h1 {
        font-size: 24pt;
    }

    .sidebar {
        width: 2in;
    }
}

/* Comparison of absolute units */
.absolute-comparison {
    /* All of these are equivalent */
    width: 96px;    /* 96 pixels */
    width: 1in;     /* 1 inch = 96px */
    width: 2.54cm;  /* 2.54 cm = 1 inch */
    width: 25.4mm;  /* 25.4 mm = 1 inch */
    width: 72pt;    /* 72 points = 1 inch */
    width: 6pc;     /* 6 picas = 1 inch */
}
Note: On screen displays, physical units like cm, mm, and in are not accurately rendered. The browser calculates them based on the assumption that 1 inch equals 96 CSS pixels, which is not physically accurate on most displays. Only use physical units in @media print stylesheets where they map to actual printed dimensions.

Relative Units: em

The em unit is relative to the font size of the element itself (or its parent, for the font-size property). This makes it a powerful unit for creating components that scale proportionally. However, the compounding nature of em can also make it tricky to work with in deeply nested structures.

How em Works for font-size

When used for the font-size property, em is relative to the parent element's font size. If a parent has a font size of 16px and you set a child to 1.5em, the child's font size will be 24px (16 * 1.5).

How em Works for Other Properties

When used for any property other than font-size -- such as padding, margin, width, or line-height -- em is relative to the element's own computed font size.

Example: How em Behaves

/* Parent has 16px font size (browser default) */
.parent {
    font-size: 16px;
}

/* Child font-size: 1.5em = 16px * 1.5 = 24px */
/* Child padding: 1em = 24px (relative to own font size) */
.child {
    font-size: 1.5em;   /* 24px */
    padding: 1em;        /* 24px -- relative to element's own font size */
    margin-bottom: 0.5em; /* 12px */
}

/* The compounding problem with nested em */
.level-1 { font-size: 1.2em; }   /* 16 * 1.2 = 19.2px */
.level-2 { font-size: 1.2em; }   /* 19.2 * 1.2 = 23.04px */
.level-3 { font-size: 1.2em; }   /* 23.04 * 1.2 = 27.65px */
.level-4 { font-size: 1.2em; }   /* 27.65 * 1.2 = 33.18px */

/* Each level gets larger and larger -- this is the em compounding issue */
Warning: The compounding behavior of em for font sizes is one of the most common pitfalls in CSS. When you nest elements that each set their font size in em, the sizes multiply at each level. This can lead to unexpectedly large or small text in deeply nested structures. Use rem for font sizes to avoid this problem.

When em Is Useful

Despite the compounding issue, em is excellent for properties that should scale with the text size of the component. For example, padding and margin on buttons or badges should grow if the font size grows:

Example: em for Scalable Components

/* A button that scales proportionally with its font size */
.button {
    font-size: 1rem;     /* Base size from root */
    padding: 0.5em 1em;  /* Scales with this button's font size */
    border-radius: 0.25em;
    border: 0.0625em solid currentColor;
}

/* Small variant -- just change font-size, spacing adjusts */
.button--small {
    font-size: 0.875rem;
    /* padding, border-radius, border all scale down automatically */
}

/* Large variant */
.button--large {
    font-size: 1.25rem;
    /* padding, border-radius, border all scale up automatically */
}

/* A badge that scales with text */
.badge {
    display: inline-block;
    font-size: 0.75rem;
    padding: 0.25em 0.5em;
    border-radius: 1em;
    background: #6366f1;
    color: white;
}

Relative Units: rem

The rem unit stands for "root em" and is always relative to the root element's font size (the <html> element). Unlike em, rem does not compound -- no matter how deeply nested an element is, 1rem always equals the same value.

By default, most browsers set the root font size to 16px. So 1rem = 16px, 0.5rem = 8px, and 2rem = 32px. This default can be changed by setting font-size on the html element.

Example: rem is Always Relative to Root

/* Default: html font-size is 16px */
html {
    font-size: 16px;
}

/* No matter how deeply nested, rem is always based on 16px */
.level-1 { font-size: 1.2rem; }   /* 16 * 1.2 = 19.2px */
.level-2 { font-size: 1.2rem; }   /* 16 * 1.2 = 19.2px -- same! */
.level-3 { font-size: 1.2rem; }   /* 16 * 1.2 = 19.2px -- same! */

/* Predictable sizing throughout the document */
h1 { font-size: 2.5rem; }     /* 40px */
h2 { font-size: 2rem; }       /* 32px */
h3 { font-size: 1.5rem; }     /* 24px */
p  { font-size: 1rem; }       /* 16px */
small { font-size: 0.875rem; } /* 14px */

/* Spacing with rem */
.section {
    padding: 2rem;        /* 32px */
    margin-bottom: 3rem;  /* 48px */
}

.card {
    padding: 1.5rem;      /* 24px */
    gap: 1rem;            /* 16px */
}

The 62.5% Font-Size Trick

One popular technique is setting the root font size to 62.5% of the browser default. Since the default is 16px, 62.5% equals 10px. This makes rem calculations much easier because 1rem = 10px, so 1.6rem = 16px, 2.4rem = 24px, and so on.

Example: The 62.5% Trick

/* Set root font size to 10px (62.5% of 16px) */
html {
    font-size: 62.5%;
}

/* Now rem values map directly to pixel values / 10 */
body {
    font-size: 1.6rem;    /* 16px */
    line-height: 1.5;
}

h1 { font-size: 3.2rem; }     /* 32px */
h2 { font-size: 2.4rem; }     /* 24px */
h3 { font-size: 2rem; }       /* 20px */
h4 { font-size: 1.8rem; }     /* 18px */
p  { font-size: 1.6rem; }     /* 16px */
small { font-size: 1.4rem; }  /* 14px */

.container {
    max-width: 120rem;   /* 1200px */
    padding: 2rem;       /* 20px */
}

.card {
    padding: 2.4rem;     /* 24px */
    border-radius: 0.8rem; /* 8px */
    margin-bottom: 1.6rem; /* 16px */
}
Tip: The 62.5% trick makes mental math much easier, but it requires you to explicitly set font-size on the body element (usually 1.6rem) to restore readable text sizes. Some developers dislike this approach because it means every single font-size declaration must use rem. The alternative is keeping the default 16px base and using a conversion reference (e.g., 1rem = 16px, 0.875rem = 14px).
Warning: Never set the root font size to a fixed pixel value like html { font-size: 10px; }. This overrides the user's browser font-size preference, which is an accessibility issue. Users who have increased their browser's default font size (for readability reasons) will have their preference ignored. Always use a percentage like 62.5% or 100% so that the base size respects the user's setting.

Percentage Units

Percentage values in CSS are always relative to something, but what they are relative to depends on the property being used. This is one of the most confusing aspects of CSS for beginners.

  • width and max-width: Relative to the parent element's width.
  • height and max-height: Relative to the parent element's height. Important: the parent must have a defined height for this to work.
  • padding and margin: Always relative to the parent element's width -- even for top and bottom padding/margin. This is a common source of confusion.
  • font-size: Relative to the parent element's font size.
  • line-height: Relative to the element's own font size.
  • transform: translate(): Relative to the element's own dimensions.
  • top, right, bottom, left (positioned elements): Relative to the containing block's dimensions.

Example: What Percentages Are Relative To

/* Parent is 800px wide */
.parent {
    width: 800px;
    height: 600px;
}

/* width: 50% = 400px (50% of parent's width) */
/* height: 50% = 300px (50% of parent's height) */
/* padding: 5% = 40px on ALL sides (5% of parent's WIDTH, not height!) */
.child {
    width: 50%;
    height: 50%;
    padding: 5%;    /* 40px on all four sides */
}

/* Creating a perfect square with the padding trick */
/* Since padding-top/bottom uses parent's width, we can create
   aspect ratios without aspect-ratio property */
.square {
    width: 50%;
    padding-bottom: 50%;  /* Same as width, creating a square */
    height: 0;
}

/* 16:9 aspect ratio container */
.widescreen {
    width: 100%;
    padding-bottom: 56.25%;  /* 9/16 = 0.5625 = 56.25% */
    height: 0;
    position: relative;
}

.widescreen-content {
    position: absolute;
    inset: 0;
}

/* Modern approach: use aspect-ratio instead */
.modern-widescreen {
    width: 100%;
    aspect-ratio: 16 / 9;
}
Note: The fact that vertical padding and margin are relative to the parent's width (not height) is intentional in the CSS specification. It ensures consistent padding around an element regardless of the dimension. This behavior was also historically used to create aspect-ratio boxes before the aspect-ratio property existed.

Viewport Units

Viewport units are relative to the dimensions of the browser viewport -- the visible area of the web page. They are essential for creating full-screen layouts, hero sections, and elements that need to respond to the browser window size rather than their parent element.

Classic Viewport Units

  • vw (viewport width) -- 1vw equals 1% of the viewport's width. 100vw spans the entire viewport width.
  • vh (viewport height) -- 1vh equals 1% of the viewport's height. 100vh spans the entire viewport height.
  • vmin -- 1vmin equals 1% of the viewport's smaller dimension (width or height, whichever is less).
  • vmax -- 1vmax equals 1% of the viewport's larger dimension (width or height, whichever is greater).

Example: Viewport Units in Action

/* Full-screen hero section */
.hero {
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Responsive font size that scales with viewport */
.hero-title {
    font-size: 5vw;  /* Gets larger on wider screens */
}

/* vmin for square elements that fit any orientation */
.profile-photo {
    width: 30vmin;
    height: 30vmin;
    border-radius: 50%;
    /* On a 1200x800 viewport: 30% of 800 = 240px */
    /* On a 800x1200 viewport: 30% of 800 = 240px (same!) */
}

/* vmax for elements that fill the larger dimension */
.background-decoration {
    width: 50vmax;
    height: 50vmax;
}

/* Full-width element that escapes its container */
.full-width {
    width: 100vw;
    margin-left: calc(-50vw + 50%);
}

Dynamic Viewport Units (dvh, svh, lvh)

On mobile browsers, the address bar and navigation chrome can appear and disappear as the user scrolls. This changes the actual visible area, causing 100vh to be either too tall (content gets cut off behind the address bar) or too short. To solve this, CSS introduced three new sets of viewport units:

  • svh, svw (small viewport) -- Based on the viewport size when the browser UI (address bar, toolbars) is fully expanded and visible. This is the smallest the viewport will be.
  • lvh, lvw (large viewport) -- Based on the viewport size when the browser UI is fully retracted and hidden. This is the largest the viewport will be.
  • dvh, dvw (dynamic viewport) -- Dynamically adjusts to the current viewport size, changing in real time as the browser UI appears and disappears.

Example: Dynamic Viewport Units for Mobile

/* Hero section that perfectly fills the visible area on mobile */
.hero {
    min-height: 100dvh;  /* Adjusts dynamically with browser chrome */
}

/* Fallback for browsers that don't support dvh */
.hero-with-fallback {
    min-height: 100vh;   /* Fallback */
    min-height: 100dvh;  /* Override if supported */
}

/* Small viewport -- guaranteed to never be clipped */
.sticky-footer {
    min-height: 100svh;
}

/* Large viewport -- uses maximum available space */
.full-background {
    min-height: 100lvh;
}

/* Using dynamic viewport units for a fixed-like element */
.bottom-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100dvw;
    padding: 1rem;
}
Tip: For mobile-first designs, prefer dvh over vh for full-screen sections. The classic 100vh on mobile Safari creates an area taller than the visible viewport (because it uses the large viewport size), causing content to be hidden behind the address bar. Using 100dvh solves this problem. Always include a vh fallback for older browsers.

The calc() Function

The calc() function allows you to perform mathematical calculations with mixed units. This is one of the most powerful features in CSS because it lets you combine absolute and relative units in a single expression.

Example: calc() with Mixed Units

/* Full width minus a fixed sidebar */
.main-content {
    width: calc(100% - 280px);
}

/* Center an element with a known width */
.centered {
    width: 800px;
    margin-left: calc(50% - 400px);
}

/* Responsive font size with minimum and maximum bounds */
.responsive-text {
    /* Base 16px + 1% of viewport width */
    font-size: calc(1rem + 1vw);
}

/* Nested calc expressions */
.complex-layout {
    padding: calc(2rem + 5%);
    width: calc(100vw - (2 * 2rem) - 280px);
    /* 100% viewport - left and right padding - sidebar */
}

/* Grid with fixed gutters and fluid columns */
.grid-3-col {
    display: grid;
    gap: 24px;
    grid-template-columns: repeat(3, calc((100% - 48px) / 3));
}

/* Combine rem and viewport units for fluid typography */
h1 {
    font-size: calc(1.5rem + 2vw);
    /* At 320px viewport: 1.5rem + 6.4px ~ 30.4px */
    /* At 1200px viewport: 1.5rem + 24px = 48px */
}

/* Using calc with CSS custom properties */
:root {
    --sidebar-width: 280px;
    --header-height: 64px;
    --padding: 2rem;
}

.content-area {
    width: calc(100% - var(--sidebar-width));
    height: calc(100vh - var(--header-height));
    padding: var(--padding);
}
Note: Inside calc(), the + and - operators must have whitespace on both sides. calc(100% -20px) is invalid -- it must be calc(100% - 20px). The * and / operators do not require whitespace but adding it improves readability.

The ch and ex Units

CSS includes two typography-specific relative units that are less commonly used but valuable in specific situations:

  • ch (character) -- Equal to the width of the "0" (zero) character in the current font. Useful for setting widths based on character count, such as limiting line length for readability.
  • ex -- Equal to the x-height of the current font (roughly the height of a lowercase "x"). Useful for fine typographic adjustments.

Example: ch and ex Units

/* Optimal line length for readability (45-75 characters) */
.prose {
    max-width: 65ch;
    margin: 0 auto;
}

/* A text input that fits approximately 20 characters */
.input-short {
    width: 20ch;
}

/* Vertical spacing relative to the x-height */
.superscript {
    vertical-align: 1ex;
    font-size: 0.75em;
}

/* Paragraph indentation based on character width */
.indented {
    text-indent: 4ch;
}

/* Monospace code block with fixed character width */
.code-line {
    max-width: 80ch;   /* Classic terminal width */
    font-family: monospace;
    overflow-x: auto;
}
Tip: The ch unit is particularly useful for setting max-width on text containers. Research shows that line lengths of 45 to 75 characters are optimal for readability. Setting max-width: 65ch creates an ideal reading experience regardless of the font size.

Comparison Table of All CSS Units

Here is a comprehensive comparison of all CSS units to help you choose the right one for each situation:

  • px -- Absolute. Relative to: nothing (1/96th of an inch). Best for: borders, shadows, fine details. Responsive: scales with zoom only.
  • em -- Relative. Relative to: parent's font-size (for font-size) or own font-size (for other properties). Best for: component spacing, padding, margins that should scale with text. Responsive: yes, but compounds in nested elements.
  • rem -- Relative. Relative to: root element font-size. Best for: font sizes, global spacing, layout dimensions. Responsive: yes, predictable, no compounding.
  • % -- Relative. Relative to: depends on property (usually parent). Best for: fluid widths, flexible layouts. Responsive: yes.
  • vw -- Relative. Relative to: 1% of viewport width. Best for: full-width elements, responsive typography. Responsive: yes.
  • vh -- Relative. Relative to: 1% of viewport height. Best for: full-height sections, hero areas. Responsive: yes (use dvh for mobile).
  • dvh/dvw -- Relative. Relative to: dynamic viewport dimensions. Best for: mobile-friendly full-screen layouts. Responsive: yes, adapts to browser chrome.
  • svh/svw -- Relative. Relative to: small viewport dimensions. Best for: ensuring content never gets hidden behind mobile browser UI. Responsive: yes.
  • lvh/lvw -- Relative. Relative to: large viewport dimensions. Best for: backgrounds and non-critical elements. Responsive: yes.
  • vmin -- Relative. Relative to: smaller viewport dimension. Best for: elements that should scale with the smaller axis. Responsive: yes.
  • vmax -- Relative. Relative to: larger viewport dimension. Best for: elements that should scale with the larger axis. Responsive: yes.
  • ch -- Relative. Relative to: width of "0" character. Best for: text container widths, input fields. Responsive: scales with font.
  • ex -- Relative. Relative to: x-height of font. Best for: fine typographic adjustments. Responsive: scales with font.
  • cm, mm, in, pt, pc -- Absolute. Relative to: physical measurements. Best for: print stylesheets only. Responsive: no.

When to Use Which Unit

Choosing the right unit depends on what you are sizing and what behavior you want. Here are clear guidelines for common scenarios:

Font Sizes: Use rem

Always use rem for font sizes. This ensures consistent sizing across your entire document and respects the user's browser font-size preference. Avoid em for font sizes due to the compounding problem. Avoid px because it does not respect user accessibility settings.

Component Internal Spacing: Use em

For padding, margin, and border-radius within a component (like a button or card), use em. This makes the spacing scale proportionally when you change the component's font size. A large button with bigger text will automatically get bigger padding.

Layout Spacing: Use rem

For spacing between sections, margins between cards, and general layout spacing, use rem. This provides consistent spacing throughout your design that scales with the user's font-size preference.

Widths: Use % or Viewport Units

For fluid layouts, use percentages for widths. For full-viewport elements, use vw. For containers, use max-width with a rem or px value and width: 100% for fluid behavior up to a maximum.

Heights: Use Content-Based or dvh

Avoid setting fixed heights when possible -- let content determine the height. For full-screen sections, use min-height: 100dvh (with a vh fallback). For scrollable containers, a fixed height in rem or px is acceptable.

Borders and Shadows: Use px

For thin borders (1px, 2px), box shadows, and outline widths, use px. These fine details do not need to scale with font size -- a 1px border should remain 1px regardless of text size.

Example: Best Practices Applied

/* Root setup */
html {
    font-size: 100%;  /* Respects user preference (usually 16px) */
}

/* Typography -- all rem */
body { font-size: 1rem; }
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.5rem; }
small { font-size: 0.875rem; }

/* Layout spacing -- rem */
.section {
    padding: 4rem 0;
    margin-bottom: 2rem;
}

/* Container -- max-width in rem, width in % */
.container {
    width: 100%;
    max-width: 75rem;   /* 1200px */
    margin: 0 auto;
    padding: 0 1.5rem;
}

/* Component (button) -- em for internal spacing */
.button {
    font-size: 1rem;
    padding: 0.625em 1.25em;
    border: 1px solid currentColor;  /* px for borders */
    border-radius: 0.25em;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);  /* px for shadows */
}

/* Grid layout -- % for columns */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
    gap: 1.5rem;
}

/* Hero section -- viewport units */
.hero {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: center;
    padding: 2rem;
}

/* Fluid typography with clamp */
.hero-title {
    font-size: clamp(2rem, 5vw, 4rem);
    /* Minimum: 2rem (32px) */
    /* Preferred: 5vw (scales with viewport) */
    /* Maximum: 4rem (64px) */
}

/* Card component */
.card {
    padding: 1.5rem;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Readable text width */
.prose {
    max-width: 65ch;
    line-height: 1.7;
}

Fluid Typography with clamp()

The clamp() function is a game-changer for responsive typography. It takes three values: a minimum, a preferred value, and a maximum. The browser uses the preferred value as long as it stays between the minimum and maximum. This eliminates the need for media queries just to adjust font sizes.

Example: Fluid Typography System

/* Fluid type scale -- no media queries needed */
:root {
    --font-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
    --font-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
    --font-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
    --font-lg: clamp(1.125rem, 1rem + 0.625vw, 1.375rem);
    --font-xl: clamp(1.25rem, 1rem + 1.25vw, 2rem);
    --font-2xl: clamp(1.5rem, 1rem + 2.5vw, 3rem);
    --font-3xl: clamp(2rem, 1.5rem + 2.5vw, 4rem);
}

/* Apply the fluid type scale */
body { font-size: var(--font-base); }
h1 { font-size: var(--font-3xl); }
h2 { font-size: var(--font-2xl); }
h3 { font-size: var(--font-xl); }
h4 { font-size: var(--font-lg); }
small { font-size: var(--font-sm); }
.caption { font-size: var(--font-xs); }

/* Fluid spacing that matches the type scale */
:root {
    --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
    --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
    --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem);
    --space-lg: clamp(1.5rem, 1rem + 2.5vw, 3rem);
    --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem);
    --space-2xl: clamp(3rem, 2rem + 5vw, 6rem);
}

em vs rem: A Practical Comparison

Understanding when to use em versus rem is a critical skill. Here is a practical side-by-side comparison showing why each unit is preferred for different properties:

Example: em vs rem Side by Side

/* APPROACH 1: All rem -- consistent but rigid */
.card-rem {
    font-size: 1rem;        /* 16px */
    padding: 1.5rem;        /* Always 24px regardless of card font size */
    margin-bottom: 1rem;    /* Always 16px */
    border-radius: 0.5rem;  /* Always 8px */
}

/* If you make a large card, spacing does NOT scale */
.card-rem--large {
    font-size: 1.25rem;     /* 20px */
    padding: 1.5rem;        /* Still 24px -- feels tight relative to larger text */
    /* You must manually increase padding too */
}

/* APPROACH 2: em for component spacing -- scales automatically */
.card-em {
    font-size: 1rem;        /* 16px */
    padding: 1.5em;         /* 24px -- scales with font size */
    margin-bottom: 1em;     /* 16px -- scales with font size */
    border-radius: 0.5em;   /* 8px -- scales with font size */
}

/* Large card automatically gets proportional spacing */
.card-em--large {
    font-size: 1.25rem;     /* 20px */
    /* padding automatically becomes 30px (1.5 * 20) */
    /* margin automatically becomes 20px (1 * 20) */
    /* border-radius automatically becomes 10px (0.5 * 20) */
}

/* BEST APPROACH: rem for font-size, em for component spacing */
.card-best {
    font-size: 1rem;           /* rem -- predictable, no compounding */
    padding: 1.5em;            /* em -- scales with this card's font size */
    margin-bottom: 1rem;       /* rem -- consistent global spacing */
    border-radius: 0.5em;      /* em -- scales with card */
    border: 1px solid #ccc;    /* px -- fine detail */
}

.card-best--large {
    font-size: 1.25rem;        /* Just change font-size */
    /* Internal spacing scales automatically via em */
    /* Global spacing stays consistent via rem */
}

Common Pitfalls and How to Avoid Them

Here are the most common mistakes developers make with CSS units and how to avoid them:

  • Using px for font-size: This ignores user accessibility preferences. Users who set their browser default to 20px or 24px will not see your text scale. Always use rem for font sizes.
  • Using em for font-size in nested elements: The compounding effect makes text unpredictable. A 1.2em font size three levels deep becomes 1.728em relative to the root (1.2 * 1.2 * 1.2). Use rem instead.
  • Setting html font-size in px: html { font-size: 10px; } breaks accessibility. Use html { font-size: 62.5%; } instead, which achieves the same 10px base while respecting user settings.
  • Using 100vh on mobile: On iOS Safari, 100vh is larger than the visible viewport when the address bar is showing. Use 100dvh with a vh fallback.
  • Forgetting calc() spacing: calc(100%-20px) is invalid. The - needs spaces: calc(100% - 20px).
  • Using vw for font-size without limits: font-size: 5vw will be tiny on small screens and enormous on large screens. Always use clamp() to set bounds.
  • Assuming 100vw equals the visible width: 100vw includes the scrollbar width on Windows browsers. This can cause a horizontal scrollbar. Use width: 100% on the body or a container instead.

Exercise 1: Build a Responsive Type System

Create a complete responsive typography system for a website. Start by setting the root font size to 100% to respect user preferences. Define CSS custom properties for a type scale using clamp() with at least 6 sizes (from extra small to heading 1). Each size should smoothly scale between a minimum rem value and a maximum rem value, using vw as the preferred middle value. Then create utility classes for each size (.text-xs, .text-sm, .text-base, .text-lg, .text-xl, .text-2xl). Also create a container class that uses max-width in rem with width: 100% and horizontal padding in rem. Finally, create a prose class with max-width: 65ch for optimal reading width. Test by changing the browser's default font size and verifying that all text scales proportionally.

Exercise 2: Scalable Button Component

Build a button component system that demonstrates the proper use of em and rem together. Create a base .btn class with font-size in rem, padding in em, border-radius in em, and border in px. Then create three size variants: .btn--sm, .btn--md, and .btn--lg that only change the font-size property. Because the spacing uses em, the padding, border-radius, and overall proportions should scale automatically. Create a demo page showing all three sizes side by side. Add a full-width variant .btn--block that uses width: 100%. Also add an icon button variant that uses the ch unit to set a square dimension. Verify that the button scales properly when the browser zoom is changed to 150% and 200%.

Exercise 3: Full-Screen Hero with Fluid Typography

Build a hero section that uses min-height: 100dvh (with a 100vh fallback) and centers its content vertically and horizontally. The hero title should use clamp() with rem, vw, and a maximum rem value for fluid sizing. Below the title, add a subtitle with a smaller fluid font size and a call-to-action button. The hero should have a container inside with max-width: 75rem and width: 100% with padding in rem. Below the hero, create a three-column grid using percentage widths and rem gaps that collapses to a single column on smaller viewports using a calc()-based approach with min() and 100%. Each card in the grid should use em for internal spacing and rem for its font size. This exercise combines every unit type discussed in this lesson.