diff --git a/src/assets/stylesheets/link.scss b/src/assets/stylesheets/link.scss
index 63da92c9709fa72367461af1006d85de03e4873a..25140d22ab01faca03fe536a0d51ab36e35a590b 100644
--- a/src/assets/stylesheets/link.scss
+++ b/src/assets/stylesheets/link.scss
@@ -108,6 +108,7 @@ a {
   display: grid;
   grid-template-columns: 1fr 1fr 1fr;
   grid-template-rows: 1fr 1fr 1fr 1fr;
+  text-align: center;
 }
 
 :local(.keypad-button) {
@@ -170,7 +171,7 @@ a {
   margin: 0;
   font-size: 64pt;
   border: 0;
-  width: 225px;
+  width: 295px;
   letter-spacing: 0.08em;
   text-align: center;
 }
diff --git a/src/hub.js b/src/hub.js
index f85a4980f3dd50dbda64fc368627d77fe42c0d1a..8bd8e2a29f9dbe957b0c20cd2798c160fbe7ede0 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -69,6 +69,7 @@ import UIRoot from "./react-components/ui-root";
 import HubChannel from "./utils/hub-channel";
 import LinkChannel from "./utils/link-channel";
 import { connectToReticulum } from "./utils/phoenix-utils";
+import { disableiOSZoom } from "./utils/disable-ios-zoom";
 
 import "./systems/personal-space-bubble";
 import "./systems/app-mode";
@@ -133,6 +134,8 @@ if (!isBotMode) {
   registerTelemetry();
 }
 
+disableiOSZoom();
+
 AFRAME.registerInputBehaviour("trackpad_dpad4", trackpad_dpad4);
 AFRAME.registerInputBehaviour("joystick_dpad4", joystick_dpad4);
 AFRAME.registerInputBehaviour("msft_mr_axis_with_deadzone", msft_mr_axis_with_deadzone);
diff --git a/src/link.html b/src/link.html
index 8f44654c0562b9ce2afd5b3bb95f482fffc213ab..954061491faa3b257adedf87df06a26cfc58f79b 100644
--- a/src/link.html
+++ b/src/link.html
@@ -7,6 +7,7 @@
     <link rel="shortcut icon" type="image/png" href="/favicon.ico"/>
     <title>Enter Code | Hubs by Mozilla</title>
     <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
 </head>
 
 <body>
diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js
index 1ab8aa47829ad5181f3796b72502d3f587241b55..a1035bc53eb9feac25298ef21fd13249edcccaed 100644
--- a/src/react-components/link-root.js
+++ b/src/react-components/link-root.js
@@ -6,10 +6,12 @@ import en from "react-intl/locale-data/en";
 import { lang, messages } from "../utils/i18n";
 import classNames from "classnames";
 import styles from "../assets/stylesheets/link.scss";
+import { disableiOSZoom } from "../utils/disable-ios-zoom";
 
 const MAX_DIGITS = 4;
 
 addLocaleData([...en]);
+disableiOSZoom();
 
 class LinkRoot extends Component {
   static propTypes = {
@@ -106,7 +108,8 @@ class LinkRoot extends Component {
               <div className={styles.enteredDigits}>
                 <input
                   className={styles.digitInput}
-                  type="number"
+                  type="tel"
+                  pattern="[0-9]*"
                   value={this.state.enteredDigits}
                   onChange={ev => {
                     this.setState({ enteredDigits: ev.target.value });
diff --git a/src/utils/disable-ios-zoom.js b/src/utils/disable-ios-zoom.js
new file mode 100644
index 0000000000000000000000000000000000000000..c2d17104a6ae4b9b8c99221fe5bec8a8f79cc909
--- /dev/null
+++ b/src/utils/disable-ios-zoom.js
@@ -0,0 +1,24 @@
+import MobileDetect from "mobile-detect";
+const mobiledetect = new MobileDetect(navigator.userAgent);
+
+export function disableiOSZoom() {
+  if (!mobiledetect.is("iPhone") && !mobiledetect.is("iPad")) return;
+
+  let lastTouchAtMs = 0;
+
+  document.addEventListener("touchmove", ev => {
+    if (ev.scale === 1) return;
+
+    ev.preventDefault();
+  });
+
+  document.addEventListener("touchend", ev => {
+    const now = new Date().getTime();
+    const isDoubleTouch = now - lastTouchAtMs <= 300;
+    lastTouchAtMs = now;
+
+    if (isDoubleTouch) {
+      ev.preventDefault();
+    }
+  });
+}