Using clamp() to scale spacing between minimum and maximum values has become quite common lately.
While extremely convenient for responsive design, accessibility zoom features reveal a subtle pitfall.
Common clamp() usage examples
For instance, when adjusting spacing with clamp(), you might typically write something like this:
:root {
--clamp-base: 16;
--clamp-viewport-min: 375;
--clamp-viewport-max: 1440;
--spacing-lg-min: 24;
--spacing-lg-max: 48;
--spacing-lg-slope: calc(
(var(--spacing-lg-max) - var(--spacing-lg-min)) / (var(--clamp-viewport-max) - var(--clamp-viewport-min))
);
--spacing-lg-intersection: calc(
(var(--spacing-lg-max) - var(--clamp-viewport-max) * var(--spacing-lg-slope)) / var(--clamp-base)
);
--spacing-lg: clamp(
calc(var(--spacing-lg-min) / var(--clamp-base) * 1rem),
calc(var(--spacing-lg-intersection) * 1rem + 100 * var(--spacing-lg-slope) * 1vw),
calc(var(--spacing-lg-max) / var(--clamp-base) * 1rem)
);
}
Without going into detail, essentially:
- On mobile, the size of
--spacing-lg-min - On desktop, the size of
--spacing-lg-max - In between, it expands and contracts smoothly.
and is a very user-friendly pattern.
What are the accessibility pitfalls?
The problem arises when trying to comply with WCAG 2.0 Success Criterion 1.4.4 "Resize text".
This criterion was originally established before smartphones became widespread, with the intent that content should remain viewable when enlarged up to 200% on a PC. However, the same condition applies to smartphone browsers.
When you actually enlarge to 200% on a smartphone, you tend to see phenomena like this.
- Text enlargement (zoom) causes
rem-based spacing to scale up along with it. - As a result, the spacing around the original content becomes excessively wide.
- This leads to the content display area becoming extremely narrow, resulting in layout breakage and poor readability.
* In reality, even looking at government sites overseas where WCAG compliance is mandatory, it seems implementation is not widely done. It is likely that practical projects consider this "not a major issue even if not implemented" due to cost-effectiveness and the fact that there is almost no actual demand for zooming to 200% on a smartphone.
How do you solve this issue?
Instead of rem, which is affected by text enlargement, use viewport-based units like vw, and it's important to avoid making spacing unnecessarily large when zooming.
However, if you specify only vw, you won't be able to take advantage of clamp()'s benefit of controlling minimum and maximum values.
This is where the min() function comes in!
min() adopts the smallest value among the values passed to it. By using this characteristic, you can switch which value is applied between normal and enlarged displays.
Here's how to write it.
--spacing-lg: min(
calc(var(--spacing-lg-min) / var(--clamp-viewport-min) * 100vw),
clamp(
calc(var(--spacing-lg-min) / var(--clamp-base) * 1rem),
calc(var(--spacing-lg-intersection) * 1rem + 100 * var(--spacing-lg-slope) * 1vw),
calc(var(--spacing-lg-max) / var(--clamp-base) * 1rem)
)
);
When displayed at 375 CSS px, the first value of min becomes 6.4vw, which is --spacing-lg-min (24px) converted to vw.
The minimum value of clamp() is 1.5rem = 24px, but at 200% enlargement it becomes 48px, so 6.4vw on the left becomes smaller and will be applied.
In other words,
- Normal display (100%):
- The
clamp()value becomes smaller than thevw-based value. - Since
min()selects the smaller value,clamp()takes effect and the display scales responsively with the viewport width.
- The
- When zoomed (200% display):
- The
remvalues insideclamp()increase with zoom, making thevw-based value smaller. - The
vw-based value is applied, and spacing is locked to a ratio of viewport width, preventing excessive enlargement.
- The
This way, you can achieve both responsive usability and accessibility considerations!
* Note: On desktop displays, enlarging padding along with the design often maintains visual balance, so --clamp-viewport-min is applied only to smartphones.
Comparing the actual behavior
Top is clamp only, bottom is min + clamp.
When you zoom to 200% on a smartphone, the difference becomes clear.
Note: On iOS Safari, you can zoom from the icon on the left of the address bar. On Android Chrome, use the icon in the top right.
Summary
- clamp() is convenient for responsive spacing adjustments
- However, zooming in can enlarge the spacing as well, which may cause layout shifts
- By combining with min(), you can achieve flexible spacing at normal zoom and controlled spacing when zoomed
If you're concerned about WCAG compliance, incorporating these implementations provides peace of mind. While implementations that "account for up to 200% zoom on mobile" are still uncommon, they preserve readability in normal viewing while reducing accessibility risks.
By the way, if you want to make fine adjustments beyond spacing, you can also use media queries with narrow breakpoints.
@media (max-width: 239px) {
/* 拡大時に適用したいCSS */
}
For example, when a 375px-wide screen is zoomed to 200%, the browser calculates the viewport width as "half the size (approximately 188px)". This allows you to target this narrow condition.
Note: The value of 239px is just an example. It's best to adjust it to leave room for larger screens in the future while ensuring normal display isn't affected.
By combining "clamp() + min()" with "narrow breakpoint media queries", you can achieve more flexible and robust accessibility compliance!
A "master of technique" who jumped from DTP into the web world and, before he knew it, mastered markup, frontend, direction, and accessibility. Active across multiple domains since Liberogic's early days, he's now a walking encyclopedia within the company. Recently, he's been diving deep into prompt-driven efficiency optimization, wondering "Can we rely more on AI for accessibility compliance?" Both his technology and thinking continue to evolve.
Futa
IAAP Certified Web Accessibility Specialist (WAS) / Markup Engineer / Frontend Engineer / Web Director