Almost every size in CSS needs a unit: font sizes, padding, widths, gaps. The unit decides what the number is measured against. Good unit choices make a design respect user settings and adapt to different screens with less code.
Absolute units: px
A CSS pixel, px, is a fixed unit. It is not tied to physical screen pixels; on a high-density phone screen, one CSS pixel may cover several device pixels. What matters is that 16px is always 16px, whatever the context.
.card {
border: 1px solid #d9d9d9;
border-radius: 8px;
}
Pixels are a good fit for small details that should not scale with text: hairline borders, small radii, shadow offsets.
CSS also defines physical units such as cm, mm, in and pt. They are only useful for print stylesheets. On screens, ignore them.
Relative units
Relative units are measured against something else: the font size, the parent's size, or the viewport. That is what makes them flexible.
rem: relative to the root font size
1rem equals the font size of the root <html> element. Browsers default that to 16px, but users can change it in their browser settings, for example to make all text larger.
h1 {
font-size: 2.5rem; /* 40px at the default, larger if the user asks */
}
p {
font-size: 1rem;
margin-bottom: 1.5rem;
}
Because every rem refers to the same root value, it is predictable anywhere in the page. And because it follows the user's preference, it is the respectful choice for text.
Do not set html { font-size: 10px; } or similar to make rem maths easier. A fixed pixel value on the root overrides the user's chosen default text size. If you want a different base, use a percentage such as html { font-size: 100%; }, which keeps the user's setting.
em: relative to the current font size
1em equals the font size of the element itself. When used for font-size, it is relative to the parent's font size.
.button {
font-size: 1.125rem;
padding: 0.5em 1em; /* scales with this button's text */
}
.button-small {
font-size: 0.875rem; /* padding shrinks automatically */
}
That is the strength of em: spacing that tracks a component's own text size. A large button gets proportionally larger padding with no extra rules.
em compounds, rem does not
The trap with em for font sizes is that it multiplies through nesting:
<ul class="menu">
<li>Products
<ul class="menu">
<li>Bread</li>
</ul>
</li>
</ul>
.menu {
font-size: 0.8em;
}
The outer list is 0.8 times the body text. The inner list is 0.8 times that, so 0.64. Each level shrinks again. Using 0.8rem instead gives both lists the same size.
| Unit | Relative to | Compounds when nested? | Typical use |
|---|---|---|---|
rem | Root (html) font size | No | Font sizes, most spacing |
em | Element's own font size | Yes, for font-size | Padding and gaps that scale with a component's text |
Percentages
A percentage is relative to something in the parent, and what depends on the property:
.sidebar {
width: 25%; /* 25% of the containing block's width */
}
.lead {
font-size: 125%; /* 125% of the parent's font size */
}
A surprising one: vertical padding and margin percentages are calculated from the width of the containing block, not its height. And height: 50% only works if the parent has a defined height; otherwise it is usually ignored.
Viewport units: vw and vh
1vw is 1% of the viewport width; 1vh is 1% of the viewport height.
.hero {
min-height: 60vh;
}
.full-bleed {
width: 100vw;
}
Use them with care:
100vwincludes the width of a vertical scrollbar on desktop, sowidth: 100vwcan cause a horizontal scrollbar.width: 100%is usually what you want.- On mobile,
100vhis measured with the browser's toolbars hidden, so content can be cut off when they are showing. Newer units such asdvhandsvhsolve this. They are covered in the Responsive Web Design course.
Never size text in vw alone, as in font-size: 4vw. It ignores the user's text size setting and does not grow when the user zooms, which is an accessibility failure. Fluid type combines rem and viewport units safely with clamp(), which the Responsive Web Design course teaches.
Unitless values
A few properties take plain numbers. The most important is line-height:
body {
line-height: 1.5; /* 1.5 times each element's own font size */
}
A unitless line height is recalculated for every element, so headings and small print each get proportional spacing. line-height: 24px would be fixed and look cramped on large headings. Zero lengths need no unit either: margin: 0.
Mixing units with calc()
calc() lets you combine units in one value:
.content {
width: calc(100% - 2rem);
}
Leave spaces around + and - inside calc(); calc(100%-2rem) is invalid.
A practical default
html {
font-size: 100%; /* respect the user's setting */
}
body {
font-size: 1rem;
line-height: 1.5;
}
h1 { font-size: 2.25rem; }
h2 { font-size: 1.75rem; }
.card {
padding: 1.5rem;
border: 1px solid #d9d9d9; /* px for hairlines */
border-radius: 0.5rem;
}
.content {
max-width: 65ch; /* about 65 characters per line */
}
ch is the width of the "0" character in the current font. max-width: 65ch is a neat way to keep lines of text at a comfortable reading length.
Testing your units
- In your browser settings, change the default font size to "Very large" and reload. Text set in
remshould grow; anything that stays tiny is fixed inpx. - Zoom to 200% with Ctrl and + (Cmd and + on macOS). The layout should reflow without overlapping or cutting off text.
- In DevTools, the Computed tab shows every length converted to pixels, which helps when tracing compounding
emvalues.
Summary
pxis fixed; use it for borders, small radii and shadow offsets.remfollows the root font size and the user's preference; use it for type and most spacing.emfollows the element's own font size and compounds when nested.- Percentages depend on the property and the parent;
vwandvhfollow the viewport. - Use unitless
line-height, never size text invwalone, and test with a larger default font size.