Stopping iOS input zoom with a handy React hook

Have your small font size cake and eat it too

Sun Nov 29 2020

There’s this stack overflow post with tons of views and upvotes and solutions on how to prevent iOS from zooming into inputs on forms. Unfortunately the advice is terrible. Make my font size 16px??? Preposterous! Disable zoooming completely??? Am I an animal?! If only there was a better way!

Well there is! Here’s the gist of it:

// Get the input some way
const input = document.getElementById('username_input');

function handleFocus() {
  input.style.fontSize = '16px';
}
function handleBlur() {
  input.style.fontSize = '';
}

input.addEventListener('touchstart', handleFocus);
input.addEventListener('blur', handleBlur);

We make the font bigger when they touch the input and we make it smaller when the input loses focus. Absolutely genius. One might wonder why touchstart instead of focus? Well it’s because when I tested it, it didn’t work when you switched directly between two different inputs…very strange.

This is only a problem on iOS though so how can we tell the device type? We could pull in a battle-tested library like react-device-detect without really thinking about it, ORRR we could read yet another stack overflow post, cross reference all the answers to figure out the actual right one, get confused which is best, end up reading the source code of react-device-detect to confirm, and we end up with

function isIOS() {
  return /iPad|iPhone|iPod/.test(navigator.platform);
}

Just kidding we have to support iPad kids so we get

function isIOS() {
  return (
    (/iPad|iPhone|iPod/.test(navigator.platform) ||
      (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
    !window.MSStream
  );
}

Now you can slap a if (!isIOS()) early return at the top of your code to let everyone else except the apple elitists enjoy your tiny tiny text.

Wait it’s 2020

JavaScript isn’t even real in 2020 if you don’t have import React at the top so we must turn this into a hook. Do you really think I use document.getElementById like some sort of savage? Behold

function useIOSInputRef() {
  const inputRef = useRef(null);

  useEffect(() => {
    const input = inputRef.current;
    if (input == null || !isIOS()) {
      return;
    }

    function handleFocus() {
      input.style.fontSize = '16px';
    }
    function handleBlur() {
      input.style.fontSize = '';
    }

    input.addEventListener('touchstart', handleFocus);
    input.addEventListener('blur', handleBlur);

    return () => {
      input.removeEventListener('touchstart', handleFocus);
      input.removeEventListener('blur', handleBlur);
    };
  }, []);

  return inputRef;
}

Then in your code you’d use it like

function MyComponent() {
  const inputRef = useIOSInputRef();
  return <input className="super-tiny-font" ref={inputRef} type="text" />;
}

Wait that Makes No Sense

Raw addEventListener calls? A hook? Jokes aside if we want to do this idiomatically, we can simply use the appropriate event handlers React allows us to specify. Thus we can better write this as:

function getIOSInputEventHandlers() {
  if (isIOS()) {
    return {};
  }

  return {
    onTouchStart: (e) => {
      e.currentTarget.style.fontSize = '16px';
    },
    onBlur: (e) => {
      e.currentTarget.style.fontSize = '';
    },
  };
}

and use it like

function MyComponent() {
  return (
    <input
      {...getIOSInputEventHandlers()}
      className="super-tiny-font"
      type="text"
    />
  );
}

With that you will never be a slave to someone else’s random font-size preferences. Take that Apple!

Back to blog