in Tech

No input zoom in Safari on iPhone, the pixel perfect way

There is a question on Stack Overflow that I check from time to time, “Disable Auto Zoom in Input ‘Text’ tag – Safari on iPhone“.

“Someday,” I would say to myself, “I’ll post my solution and write a blog post as well.”

That day has arrived.

What is the problem, exactly?

Sometimes in Safari (or any other browser built on UIWebView or WKWebView) on iPhone, if the user focuses a text input field, the browser will zoom in a little.

You can try this yourself in Example 1 (on iPhone, of course). (Full size screenshots: 1, 2)

(Screenshots in this post are from iPhone 7 Plus running iOS 11.4.1, resized from their original 3x size down to 1x and saved as JPEG. Full size links are to the original PNG files.)

The cause of this issue, as the top-voted Stack Overflow answer notes, is the font size of the input field text. If the font size is less than 16px, then Safari will zoom in when the field is focused. It does not matter if other units like em or rem are used, as long as the computed font size is less than 16px.

One might presume that Safari does this to make the text more readable, but this only occurs on iPhone and not iPad, where the same readability concerns may also apply.

What are the solutions so far?

Current Stack Overflow answers boil down to two approaches:

  • Set the font size to 16px.
  • Disable page zooming, by adding maximum-scale=1.0 and/or user-scalable=no to the <meta name="viewport"> tag.

Neither solution is free of trade-offs:

  • Setting the font size to 16px sacrifices visual design. Increasing the text size for one element without adjusting other elements may disrupt the visual hierarchy of the page.
  • Disabling page zooming hurts usability. There are many reasons why users want / need the ability to zoom in.

What do you suggest?

This is a method that trades CSS complexity for preserving visual design and maintaining usability. In essence:

  1. Style the input field so that it is larger than its intended size, allowing the logical font size to be set to 16px.
  2. Use the scale() CSS transform and negative margins to shrink the input field down to the correct size.

A concrete example

Suppose, as in Example 1, the input field is originally styled as follows:

input[type="text"] {
    /* styles to be adjusted */
    border-radius: 5px;
    font-size: 12px;
    line-height: 20px;
    padding: 5px;
    width: 100%;
}

(Pixel lengths are used here for simplicity; relative units like em or rem are preferred for real world use.)

First, enlarge the input field by increasing all dimensions by 16 ÷ 12 = 133.33%:

input[type="text"] {
    /* enlarge by 16/12 = 133.33% */
    border-radius: 6.666666667px;
    font-size: 16px;
    line-height: 26.666666667px;
    padding: 6.666666667px;
    width: 133.333333333%;
}

Notice that the font size is now 16px, which will not trigger any zoom on focus.

Next, reduce the input field using the scale() CSS transform by 12 ÷ 16 = 75%:

input[type="text"] {
    /* enlarge by 16/12 = 133.33% */
    border-radius: 6.666666667px;
    font-size: 16px;
    line-height: 26.666666667px;
    padding: 6.666666667px;
    width: 133.333333333%;

    /* scale down by 12/16 = 75% */
    transform: scale(0.75);
    transform-origin: left top;
}

You can see the result of this styling in Example 2. (Full size screenshots: 1, 2)

Note that at this point, there is extra white space to the right and below the input field. This is because scale() only affects the field’s visual appearance; the field’s larger logical size remains unchanged.

Set negative margins to remove this extra space:

input[type="text"] {
    /* enlarge by 16/12 = 133.33% */
    border-radius: 6.666666667px;
    font-size: 16px;
    line-height: 26.666666667px;
    padding: 6.666666667px;
    width: 133.333333333%;

    /* scale down by 12/16 = 75% */
    transform: scale(0.75);
    transform-origin: left top;

    /* remove extra white space */
    margin-bottom: -10px;
    margin-right: -33.333333333%;
}

Example 3 contains this final styling: (Full size screenshots: 1, 2)

There will be notes

A few points to keep in mind:

  • A 16px font scaled down to 12px will appear very close, but not identical, to the same font set at 12px. In particular, fonts usually have hinting that increases legibility at smaller sizes. Be sure to test the input field’s readability after applying this method.
  • Other input field styles may also be affected, due to rounding in the enlarge-then-shrink process. For instance, a 1px border may end up appearing slightly thinner or lighter. (If you examine the full size screenshots above, you will note that the input field has become about 0.33px shorter in height.) This would not normally be noticeable to users but be sure to test the result.
  • Due to the previous two points, I would suggest limiting this approach to iOS devices only. (Yes, with user agent detection if necessary.)
  • Did I mention, be sure to test the result. 😜

Also, while researching for this post I came across this pen by Jakob Eriksen that exactly illustrates this technique. What can I say, great minds think alike. 😂

Conclusion

With a little CSS trickery, it is possible to implement a given design and avoid the input zoom issue in Safari at the same time. I hope to see this method used on more sites in the future.

P.S. If you liked this solution, please upvote my answer on Stack Overflow and help spread the word. Thanks!

Write a Comment

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

12 Comments

  1. But why we didn’t count both way?
    If we have input with font-size 14px
    We can set font-size to 16px
    Calc scale strength as 14/16 -> transform: scale(0.875);
    And calc scale for smaller styles already

    For example for height: 25px
    We can count 16/14 -> height: calc(25px * 1.142857);

    • In the example, by enlarging line height and padding, the height of the input field is also increased. You could set an explicit (scaled up) height if you prefer.

      Does this address your question?