Best Way to Programmatically Zoom a Web Application

Avatar of Michael Romanov
Michael Romanov on (Updated on )

Website accessibility has always been important, but nowadays, when we have clear standards and regulations from governments in most countries, it’s become even more crucial to support those standards and make our projects as accessible as they can be.

The W3C recommendation provides 3 level of conformance: A, AA and AAA. To be at the AA level, among other requirements, we have to provide a way to increase the site’s font size:

1.4.4 Resize text: Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality. (Level AA)

Let’s look at solutions for this and try to find the best one we can.

Update(09/25/17): It turns out that WCAG doesn’t require a text resizing custom solution in addition to what user-agents provide. We just have to make sure a website looks OK as a result of resizing. Yet if we still want to scale elements for any reason, below is a thorough analysis of different methods for doing that.

Incomplete Solution :  CSS zoom

The first word which comes up when we talk about size changing is zoom. CSS has a zoom property and it does exactly what we want — increases size.

Let’s take a look at a common design pattern (that we’ll use for the rest of this article): a horizontal bar of navigation that turns into a menu icon at a certain breakpoint:

This is what we want to happen. No wrapping and the entire menu turns into a menu icon at a specified breakpoint.

The GIF below shows what we get with zoom approach applied to the menu element. I created a switcher which allows selecting different sizes and applies an appropriate zoom level:

Check out the Pen here if you want to play with it.

The menu goes outside visible area because we cannot programmatically increase viewport width with zoom nor we can wrap the menu because of the requirement. The menu icon doesn’t appear either, because the screen size hasn’t actually changed, it’s the same as before we clicked the switcher.

All these problems, plus, zoom is not supported by Firefox at all anyway.

Wrong Solution: Scale Transforms

We can get largely the same effect with transform: scale as we got with zoom. Except, transform is more widely supported by browsers. Still, we get the exact same problems as we got with zoom: the menu doesn’t fit into the visible area, and worse, it goes beyond the vertical visible area as well because page layout is calculated based on an initial 1-factor scale.

See the Pen Font-switcher–wrong-scale by Mikhail Romanov (@romanovma) on CodePen.

Another Incomplete Solution :  rem and html font-size

Instead of zooming or scaling, we could use rem as the sizing unit for all elements on the page. We can then change their size by altering html element’s font-size property, because by its definition 1rem equals to html‘s font-size value.

This is a fairly good solution, but not quite perfect. As you can see in the following demo, it has the same issues as previous examples: at one point it doesn’t fit horizontally because required space is increased but the viewport width stays intact.

See the Pen Font-switcher–wrong-rem by Mikhail Romanov (@romanovma) on CodePen.

The trouble, in a sense, is that the media queries don’t adjust to the change in size. When we go up in size, the media queries should adjust accordingly so that the effect at the same place would happen before the size change, relative to the content.

Working Solution:  Emulate Browser Zoom with Sass mixin

To find inspiration, let’s see how the native browser zoom feature handles the problem:

Wow! Chrome understands that zooming actually does change the viewport. The larger the zoom, the narrower the viewport. Meaning that our media queries will actually take effect like we expect and need them to.

One way to achieve this (without relying on native zoom, because there is no way for us to access that for our on on-page controls as required by AA) is to somehow update the media query values every time we switch the font size.

For example, say we have a media query breakpoint at 1024px and we perform a 200% zoom. We should update that breakpoint to 2048px to compensate for the new size.

Shouldn’t this be easy? Can’t we just set the media queries with rem units so that when we increase the font-size the media queries automatically adjust? Sadly no, that approach doesn’t work. Try to update media query from px to rem in this Pen and see that nothing changes. The  layout doesn’t switch breakpoints after increasing the size. That is because, according to standards, both rem and em units in media queries are calculated based on the initial value of html element font-size which is normally 16px (and can vary).

Relative units in media queries are based on the initial value, which means that units are never based on results of declarations. For example, in HTML, the em unit is relative to the initial value of ‘font-size.

We can use power of Sass mixins to get around this though! Here is how we’ll do it:

  • we’ll use a special class on html element for each size(font-size--s, font-size--m, font-size--l, font-size--xl, etc.)
  • we’ll use a special mixin, which creates a media query rule for every combination of breakpoint and size and which takes into account both screen width and modifier class applied to html element
  • we’ll wrap code with this mixin everywhere we want to apply a media-query.

Here is how this mixin looks:

$desktop: 640px;
$m: 1.5;
$l: 2;
$xl: 4;

// the main trick is here. We increase the min-width if we increase the font-size
@mixin media-desktop {
  html.font-size--s & {
    @media (min-width: $desktop) {
      @content;
    }
  }

  html.font-size--m & {
    @media (min-width: $desktop * $m) {
      @content;
    }
  }

  html.font-size--l & {
    @media (min-width: $desktop * $l) {
      @content;
    }
  }

  html.font-size--xl & {
    @media (min-width: $desktop * $xl) {
      @content;
    }
  }
}
.menu {
  @include media-desktop {
    &__mobile {
      display: none;
    }
  }
}

And an example of the CSS it generates:

@media (min-width: 640px) {
  html.font-size--s .menu__mobile {
    display: none;
  }
}
@media (min-width: 960px) {
  html.font-size--m .menu__mobile {
    display: none;
  }
}
@media (min-width: 1280px) {
  html.font-size--l .menu__mobile {
    display: none;
  }
}
@media (min-width: 2560px) {
  html.font-size--xl .menu__mobile {
    display: none;
  }
}

So if we have n breakpoints and m sizes, we will generate n times m media query rules, and that will cover all possible cases and will give us desired ability to use increased media queries when the font size is increased.

Check out the Pen below to see how it works:

See the Pen Font-switcher–right by Mikhail Romanov (@romanovma) on CodePen.

Drawbacks

There are some drawbacks though. Let’s see how we can handle them.

Increased specificity on media-query selectors.

All code inside the media query gets additional level of specificity because it goes inside html.font-size — x selector. So if we go with the mobile first approach and use, for example, .no-margin modifier on an element then desktop normal style can win over the modifier and desktop margins will be applied.

To avoid this we can create the same mixin for mobile and wrap with our mixins not only desktop but also mobile CSS code. That will balance specificity.

Other ways are to handle every special case with an individual approach by artificially increasing specificity, or creating mixin with desired functionality(no margin in our example) and putting it not for mobile only but also into every breakpoint code.

Increased amount of generated CSS.

Amount of generated CSS will be higher because we generate same CSS code for every size.

This shouldn’t be an issue if files are compressed with gzip (and that is usually the case) because it handles repeated code very well.

Some front-end frameworks like Zurb Foundation use built-in breakpoints in JavaScript utilities and CSS media queries.

That is a tough one. Personally, I try to avoid the features of a framework which depends on the screen width. The main one which can be often missed is a grid system, but with the rise of flexbox and grid, I do not see it to be an issue anymore. Check out this great article for more details on how to build your own grid system.

But if a project depends on a framework like this, or we don’t want to fight the specificity problem but still want to go with AA, then I would consider getting rid of fixed height elements and using rems together with altering the html element font-size to update the layout and text dimensions accordingly.


Thank you for reading! Please let me know if this helps and what other issues you faced conforming to the 1.4.4 resize text W3C Accessibility requirement.