Text zoom issues are the worst, I made it better
To comply with the Web Content Accessibility Guidelines (WCAG) 2.2, we need to make sure that our website is usable at 200% text zoom. As a web developer, these A11Y issues are often the most annoying to fix! Just last week, the euphoria of wrapping up a website project was short-lived when my colleague, Caitlin de Rooij at Level Level, played the bearer of bad news - we had a 200% text zoom debacle on our hands. Not again! To tackle this problem once and for all, I came up with a solution that makes dealing with text zoom issues a lot easier.
Text zoom media queries
To make a website responsive we often use media queries. These media queries are used to change the layout of the website depending on the screen size. When dealing with text zoom issues I always wanted to use some sort of media query to change the layout depending on the text zoom level. However, this is not possible. ... "argh". But! I found a way to make this possible using my best friend JavaScript. As JavaScript isn't able to detect the text zoom level, we need to use some little tricks to make this possible.
How to implement the text zoom hack
Our text zoom hack consists of two parts. First we need to create a point of reference. Second we need to create some sort of media query that uses this point of reference to detect the text zoom level.
Creating a point of reference
We can do this by creating a <div>
with a fixed width of 1em
, as 1em
is
the current root font-size. We need to make sure that this div is always the
first element in our body and is completely hidden for the user. Your HTML And
CSS should look something like this:
<body>
<div
id="text-zoom-reference"
aria-hidden="true"
class="text-zoom-reference"
></div>
</body>
.text-zoom-reference {
width: 1em;
position: absolute;
top: 0;
left: 0;
z-index: -1;
pointer-events: none;
}
Creating a custom media query
With a reference point established, it's time to delve into some JavaScript to
identify the text zoom level. The strategy here is to compare the width of our
reference element with 16px
, which is the default root font size. The
JavaScript code for this logic would appear as follows:
const referenceElement = document.getElementById('text-zoom-reference');
let fontSize;
// Add more levels to this array if different zoom levels are needed
const zoomLevels = [
{ factor: 1.75, class: 'text-zoom-175' },
{ factor: 1.5, class: 'text-zoom-150' },
{ factor: 1.25, class: 'text-zoom-125' },
{ factor: 1, class: 'text-zoom-100' },
];
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const currentSize = entry.target.offsetWidth;
if (currentSize === fontSize) {
return;
}
fontSize = currentSize;
const zoomFactor = fontSize / 16;
zoomLevels.forEach(zoomLevel => {
document.body.classList.remove(zoomLevel.class);
if (zoomFactor >= zoomLevel.factor) {
document.body.classList.add(zoomLevel.class);
}
});
}
});
resizeObserver.observe(referenceElement);
I want to take some time to explain this code line by line, so you can get a real grip on what happens here.
- First we get the reference element from the DOM. We use this element to compare the width of the reference element with the default font size.
- We define a
fontSize
variable that will be changed according to the current font size. - We create an array of zoom levels. This array contains the zoom factor and the class that should be added to the body when the zoom factor is reached.
- We create a
ResizeObserver
that observes changes of the reference elements dimensions. - Inside the "for loop", We get the current width of the reference element and check if it is different from the previous font size.
- If needed, we update the font size.
- We calculate the zoom factor by dividing the current font size by the default font size.
- We loop over the zoom levels array and remove all text zoom classes from the body.
- We check if the zoom factor is larger than the zoom factor of the current zoom level. If so, we add the corresponding text zoom class to the body.
- Finally, we have to tell the
resizeObserver
to observe thereferenceElement
.
How to use it
Now that we have our text zoom hack in place, we can use it to fix our text zoom issues. We can use the text zoom classes to change the layout of our website depending on the text zoom level. For example, we can use the following CSS to change the layout of our element when the text zoom level is 150%:
.element {
display: flex;
flex-direction: row;
}
.text-zoom-150 .element {
flex-direction: column;
}
Demo
I've found this hack to be particularly useful with grid layouts. For instance, in a 4-column layout, increasing text zoom retains the four columns, which can look cramped and hinder readability. With this hack, you can transition to a three- or two-column layout at certain text zoom levels, significantly enhancing readability. Below is a demo illustrating this, allowing you to experience the layout alteration as you tweak the text zoom (not the page zoom!).
Supercharging with TailwindCSS
Being a TailwindCSS aficionado, I wanted to leverage
this text zoom hack within its ecosystem, leading to the birth of a plugin.
Here’s how you can integrate it into your tailwind.config.js
. Just paste this
code in the plugins
array:
plugin(function ({ addVariant, e }) {
const zoomVariants = {
'text-zoom-md': 'text-zoom-125',
'text-zoom-lg': 'text-zoom-150',
'text-zoom-xl': 'text-zoom-175',
};
Object.keys(zoomVariants).forEach(key => {
addVariant(key, ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${zoomVariants[key]} .${e(
`${key}${separator}${className}`
)}`;
});
});
});
});
You can modify the zoom levels to your liking by changing the zoomVariants
object. The key is the name of the variant, and the value is the class that
should be added to the body when the zoom level is reached. Make sure to require
'plugin' at the top of your tailwind.config.js
:
const plugin = require('tailwindcss/plugin');
Now you can use the text zoom modifiers within your TailwindCSS code:
<div class="grid grid-cols-4 text-zoom-lg:grid-cols-2"></div>
Wrapping Up
While the demo predominantly showcased layout alterations, the potential applications are boundless. You could adjust padding, margins, and much more—unleash your creativity! Just bear in mind that hiding content using this hack is off-limits as it could result in a WCAG violation. I hope this article will make your websites more accessible and your life as a web developer a little easier.