Fluid Typography and Spacing
Fluid values scale smoothly between minimum and maximum without step-function breakpoints — the clamp() function makes this a single CSS declaration.
- Explain why fixed type sizes create jumps at breakpoints
- Use viewport units for type scaling while understanding their limits
- Apply clamp() to create a fluid scale with a guaranteed minimum and maximum
- Extend the pattern to padding, margin, and gap with CSS custom properties
A layout that changes at three breakpoints is responsive. A layout whose type
and spacing flow between those breakpoints feels polished. The difference is
clamp() — a CSS function that keeps a value between a minimum and maximum
while letting it scale fluidly in between.
The problem with fixed sizes
h1 {
font-size: 2.5rem;
}
@media (min-width: 768px) {
h1 {
font-size: 3.5rem;
}
}This works, but on a viewport just above 768px the heading jumps from 2.5rem to 3.5rem in one step. Shrink past 768px and it jumps back. Resize the browser quickly and you can see the snap. It is not wrong, but it is a step function where a slope would look better.
Viewport units for type
Viewport units scale continuously with the
viewport width. 1vw is one percent of the viewport width, so:
h1 {
font-size: 5vw;
}This scales smoothly. But the extremes are brutal: at 320px wide, 5vw is 16px
— the same size as body text. At 1440px wide, it is 72px — oversized for a
heading. Pure viewport units give up the concept of a minimum readable size.
clamp(): three values, one line
clamp(min, preferred, max) asks the browser to use the preferred value but
never let the result fall below the minimum or rise above the maximum.
h1 {
font-size: clamp(1.75rem, 5vw, 3.5rem);
}Break this down:
1.75rem— the minimum. Below ~560px viewport width, the heading stays at 1.75rem rather than shrinking further.5vw— the preferred. Between 560px and 1120px, the font scales with the viewport.3.5rem— the maximum. Above ~1120px, the heading stays at 3.5rem rather than becoming enormous.
No media query needed. The browser calculates which value applies based on the current viewport width.
The crossover points (where min takes over and where max takes over) are
implicit: min activates when preferred would be smaller than it, and max
activates when preferred would be larger. You can work them out:
1.75rem / 5vw = 35rem = 560px for the lower crossover, and
3.5rem / 5vw = 70rem = 1120px for the upper.
Sizing the preferred value
The preferred value is usually a viewport-relative expression. Common patterns:
/* Simple vw percentage */
font-size: clamp(1rem, 2.5vw, 1.5rem);
/* Mix of rem and vw — scales but keeps a stable base */
font-size: clamp(1rem, 1rem + 1.5vw, 1.75rem);The 1rem + 1.5vw form is often smoother because it does not scale from zero
— it starts at 1rem and grows by 1.5vw. The result: on a narrow screen the
minimum kicks in quickly, and the scaling is gentler overall.
Applying the pattern to spacing
The same logic works for any length property — padding, margin, gap,
border-radius. Fluid spacing makes the layout breathe naturally instead of
snapping between fixed padding values.
.section {
padding-block: clamp(2rem, 8vh, 6rem);
padding-inline: clamp(1rem, 5%, 4rem);
}padding-inline with 5% scales with the container width, keeping gutters
proportional. padding-block uses vh to scale vertical breathing room with
the screen height.
A fluid spacing scale with custom properties
Rather than writing clamp() everywhere, define a reusable spacing scale as
CSS custom properties:
:root {
--space-xs: clamp(0.5rem, 1vw, 0.75rem);
--space-sm: clamp(0.75rem, 2vw, 1.25rem);
--space-md: clamp(1rem, 3vw, 2rem);
--space-lg: clamp(1.5rem, 5vw, 3rem);
--space-xl: clamp(2rem, 7vw, 5rem);
}Then use them consistently:
.card {
padding: var(--space-md);
gap: var(--space-sm);
}
.hero {
margin-block: var(--space-xl);
}Define the scale once; update it once. Every component that references these variables responds to viewport changes automatically.
Fluid scaling is not a replacement for breakpoints — it is a complement to
them. Use clamp() for continuous properties like font size and spacing. Use
media queries for structural changes like column count or navigation layout.
Trying to fluid-scale your way out of a layout change leads to awkward
intermediate states.
Where to go next
Type and spacing scale fluidly. Next: Responsive Images — letting the browser choose the right image source for the current screen size and pixel density, without JavaScript.
Mobile-First Design
Mobile-first means writing base styles for the smallest screen and adding complexity for larger screens — a discipline that produces leaner, more maintainable CSS.
Responsive Images
The browser can choose from multiple image sources based on screen size and pixel density — if you provide the options with srcset and sizes.