The Best CSS Unit for Responsive Web Design

    Matt Watson
    By Matt Watson · CEO of Full Scale, 4x Founder, Author of Product Driven
    10 min read
    Text on a dark background reads: "no single best unit match it to the job. The Best CSS Unit for Responsive Web Design. rem, clamp(), container queries, and what to use when.
    In this article

    There is no single best CSS unit. Anyone who tells you “just use rem for everything” or “px is dead” is selling you a rule, not an answer.

    The real answer is a short list of units, each matched to the job it does well.

    Get that mapping right and your layout holds up on a 320px phone, a 27-inch monitor, and every screen in between, without a wall of media queries propping it up. Get it wrong and you spend your afternoons patching text that overflows its box or a hero section that swallows the whole screen on mobile.

    This guide covers the units that actually matter for responsive work in 2026, what each one is good at, and the small set you should reach for by default.

    The short answer

    If you only remember one thing, remember this mapping:

    JobUseWhy
    Font sizesremScales with the user’s browser setting, so it respects accessibility
    Fluid type and spacingclamp() with rem and vwOne line replaces a stack of media queries
    Layout structure and grids% and frSizes relative to the parent or the grid container
    Component-level responsivenesscqi / cqwThe element reacts to its own space, not the viewport
    Full-height sections on mobiledvh (or svh)Survives the mobile address bar showing and hiding
    Line length (the measure)chCaps text at a readable number of characters
    Borders and hairlinespxA 1px border should be 1px everywhere

    The rest of this guide explains why. If you want the reasoning behind any row, jump to that section.

    Absolute units are fixed, and that’s the problem

    Every CSS unit falls into one of two buckets, and the bucket tells you most of what you need to know.

    Absolute units are fixed. A value set in px, cm, mm, in, pt, pc, or q renders at the same size no matter the screen or the user’s settings. These come from print, and they behave like print: rigid. One caveat worth knowing, since careful readers will catch it: on a screen these are all defined against the CSS reference pixel, not a physical inch. 1in equals 96 CSS pixels, which is not the same as an inch of glass.

    That rigidity is the problem for the web. A heading locked to 48px looks fine on your laptop and cramped on a phone. Worse, font sizes set in px ignore the reader who bumped their default text size up for readability. You overrode their choice without meaning to.

    There is one absolute unit worth keeping: px. Use it for the things that really should be fixed, like a 1px border or a hairline divider. For almost everything else, reach for a relative unit. You can read the full list of CSS length units on MDN.

    Relative units size themselves against something else, whether that’s a parent’s font size, the root font size, the viewport, or the element’s own container. That “something else” is what makes them responsive. When the reference changes, the value changes with it.

    rem and em both size against text, just not the same text

    These two look almost identical and trip up a lot of developers. The difference is what they measure against.

    em is relative to the font size of the current element (or its parent, for properties like font-size itself). rem is relative to the font size of the root element, the <html> tag.

    That root reference is what makes rem predictable. Set the root once and a 1.5rem heading is the same size everywhere, no matter how deeply it sits in the markup.

    html {
      font-size: 100%; /* respects the user's browser default, usually 16px */
    }
    
    h1 {
      font-size: 2.5rem;  /* 40px when the root is 16px */
    }
    
    p {
      font-size: 1rem;    /* matches the user's chosen size */
    }

    em compounds, and that is both its strength and its trap. Nest three elements that each set font-size: 1.2em and the text balloons, because each level multiplies the one above it. That makes em great for spacing that should scale with a component’s own text (padding on a button, say) and risky for global type.

    Use rem for font sizes. Use em for spacing that should track the element’s own font size. The accessibility payoff is the real reason: because rem keys off the user’s browser setting, someone who sets their default to 20px gets your whole layout scaled up, exactly as they asked. The WCAG guidance on resizing text is built around this behavior.

    One more relative-to-text unit worth knowing: lh and rlh size against the line height of the current element and the root. They’re handy for vertical rhythm, like spacing that lines up with your baseline grid.

    Percentages and fr handle layout structure

    % is relative to the parent element. A child set to width: 50% takes half of whatever its parent offers. Simple, and still the backbone of a lot of fluid layouts.

    The catch is that percentages refer to different things depending on the property. width: 50% is half the parent’s width, but padding-top: 50% is also half the parent’s *width*, not its height. That quirk surprises people, though it is also the classic trick for holding an aspect ratio (these days aspect-ratio does it more cleanly).

    For grid and flex layouts, the fr unit is usually the better tool. fr means “one fraction of the free space” in a grid container.

    .layout {
      display: grid;
      grid-template-columns: 1fr 3fr; /* sidebar takes 1 share, main takes 3 */
      gap: 2rem;
    }

    fr divides up what’s left after fixed-size items are placed, so your columns flex with the container instead of fighting it. It removes most of the percentage math you used to do by hand.

    Viewport units scale with the window

    Viewport units size against the browser window itself. The viewport is the visible area of the page, and it changes with the device.

    • vw is 1% of the viewport width. 100vw is the full width.
    • vh is 1% of the viewport height.
    • vmin is 1% of the smaller of the two dimensions.
    • vmax is 1% of the larger dimension.

    These are powerful when something should scale with the screen itself. A hero section that fills the window is the obvious case, and they also work well for type that grows on big displays or spacing that opens up on a tablet. vmin is handy when you want an element to fit on screen in either orientation, since it tracks whichever side is shorter.

    Two warnings. First, 100vw includes the scrollbar width on desktop, so it can cause a sliver of horizontal scroll. Second, raw viewport units don’t know about your reader’s font-size setting, so type sized purely in vw ignores accessibility. That’s why you rarely use them alone for text. You combine them, which is what clamp() is for.

    dvh, svh, and lvh fix the mobile full-height bug

    For years, 100vh on mobile had an ugly bug. The browser counted the full screen height, including the space behind the address bar. So a section set to height: 100vh got cut off, or jumped, as the address bar slid in and out while you scrolled.

    Building a development team?

    See how Full Scale can help you hire senior engineers in days, not months.

    The fix is the dynamic viewport family:

    • svh is the small viewport height (address bar visible).
    • lvh is the large viewport height (address bar hidden).
    • dvh is the dynamic viewport height, which updates live as the bar shows and hides.
    .fullscreen-hero {
      min-height: 100dvh; /* fills the screen and stays correct on mobile */
    }

    Reach for dvh when you want a true full-height section on a phone. One caution, though: because dvh recalculates as the bar slides, a dvh-sized box resizes mid-scroll, which can read as jank. When you want a box that just stays put, use svh. Save dvh for the cases where the live update is the point.

    The same dv*, sv*, and lv* prefixes exist for width too, but you rarely need dynamic width, since the address bar changes height, not width. All of these have been safe to ship across modern browsers since 2023, so no polyfill required. The web.dev writeup on viewport units walks through the whole set.

    Container query units size against the element, not the screen

    This is the biggest shift in responsive CSS, and it is the one most worth learning if you haven’t yet.

    A card component doesn’t care how wide the *window* is. It cares how much room it has where it sits, whether that’s a narrow sidebar or a wide main column. Media queries can’t see that. Container queries can.

    Container query units measure against the nearest ancestor marked as a query container:

    • cqw is 1% of the container’s width.
    • cqh is 1% of the container’s height.
    • cqi is 1% of the container’s inline size (width in most writing modes).
    • cqb is 1% of the container’s block size.
    • cqmin and cqmax track the smaller and larger of the two.
    .card-grid {
      container-type: inline-size;
    }
    
    .card h2 {
      font-size: clamp(1rem, 5cqi, 1.5rem); /* scales with the card, not the screen */
    }

    The same card now looks right in a three-column grid and in a single narrow column, with no breakpoints. The component is genuinely reusable.

    Two catches keep this honest. Setting container-type: inline-size turns on containment, which can change how the element sizes itself, so you usually mark a *wrapper* as the container rather than the element you’re styling. And an element can’t query itself, only an ancestor, which is the other reason for the wrapper. Like the dynamic viewport units, container queries and their units have been safe to ship since 2023. The MDN container query length units reference covers the details.

    ch caps your text at a readable width

    1ch is roughly the width of the 0 character in the current font. It sounds niche, but it solves a real readability problem.

    Long lines of text are hard to read. The comfortable range is about 45 to 75 characters per line, and ch lets you cap it directly:

    .prose {
      max-width: 65ch; /* keeps body text at a readable measure */
    }

    This beats setting a pixel max-width, because ch adjusts when the font or font size changes, so the measure stays right.

    clamp() does fluid sizing without media queries

    The modern answer to “how do I size this responsively without ten media queries” is the math functions, and clamp() is the star.

    clamp(MIN, PREFERRED, MAX) picks the preferred value but never lets it drop below the minimum or rise above the maximum. Pair a rem floor and ceiling with a vw or cqi middle and you get type that scales smoothly between two sane limits:

    h1 {
      font-size: clamp(2rem, 5vw + 1rem, 4rem);
    }

    That heading never goes below 32px and never above 64px, and it scales fluidly in between. The + 1rem term matters: a bare vw middle ignores user zoom, and adding a rem term plus the rem floor and ceiling keeps the heading responsive to the reader’s font setting.

    min() and max() are the simpler siblings. width: min(90%, 70ch) means “90% of the parent, but never wider than 70 characters,” which is a clean way to build a centered content column.

    What to actually reach for

    Pull it together and the defaults are short:

    • Font sizes: rem, almost always.
    • Fluid type and spacing: clamp() with rem limits and a vw or cqi middle.
    • Layout structure: fr in grid, % where it’s simplest.
    • Component responsiveness: container query units (cqi) over viewport units when the element should react to its own space.
    • Full-screen sections: dvh or svh instead of vh on anything mobile users will see.
    • Line length: ch to cap your text measure.
    • Hairlines and borders: px, because some things really are fixed.

    You don’t need every unit on every project. You need to stop reaching for px font sizes out of habit, and you need container query units and clamp() in your toolkit, because together they kill most of the media queries you used to write.

    Frequently asked questions

    Is px or rem better for responsive design?

    rem is better for font sizes because it scales with the user’s browser setting, which keeps your site accessible to people who change their default text size. Keep px for things that should stay fixed, like a 1px border. Using rem for type and px for borders is a common, sensible split.

    What is the difference between em and rem?

    em is relative to the font size of the current element, so it compounds when elements are nested. rem is relative to the root element’s font size, so it stays consistent everywhere. Use rem for global type and em for spacing that should scale with a component’s own font size.

    Should I use vh or dvh for full-height sections?

    Use a dynamic unit on mobile. The older vh miscounts height on phones because it ignores the address bar sliding in and out, which cuts off or shifts your content. dvh updates as the bar moves, while svh stays at the smaller, stable size. Reach for svh when you want the box to hold still and dvh when you want it to track the bar.

    What are container query units?

    Container query units (cqw, cqh, cqi, cqb, and the min and max variants) size an element relative to its nearest container instead of the whole viewport. They let a component respond to the space it actually sits in, so the same card works in a narrow sidebar and a wide column with no media queries.

    Do I still need media queries?

    Less often than you used to. clamp() handles most fluid type and spacing, and container query units handle component-level responsiveness. You still reach for media queries for big layout shifts, like collapsing a multi-column grid into one column, and when you do, author the breakpoints in em so they respond to user zoom.

    Build it with people who know the difference

    Picking the right unit is a small skill that separates a layout that holds up from one you babysit. If you’d rather hire frontend developers who already work this way than train for it, Full Scale staffs senior engineers for frontend and web development projects.

    Book a call and tell us what you’re building.

    Ready to add senior engineers to your team?

    Book a 15-minute call. Tell us your stack and where the gaps are, and we'll show you the engineers we'd put on your team.