diff --git a/dictionaries/privacyidea.definition.json b/dictionaries/privacyidea.definition.json
index c3718d8be988d067d7887b119050e5691b2cfe42..0adda9ce6e6619f3819dc5e95be7615a23010251 100644
--- a/dictionaries/privacyidea.definition.json
+++ b/dictionaries/privacyidea.definition.json
@@ -8,7 +8,11 @@
     "cs": "Služba ke které přistupujete vyžádala vícefaktorovout autentizaci. Použijte jednu z vašich zaregistrovaných metod kliknutím na jedno z tlačítek nížě."
   },
   "otp_help": {
-    "en": "Enter the one time password from the smartphone application.",
-    "cs": "Vložte jednorázový kód, například z TOTP aplikace."
+    "en": "Enter a verification code from authenticator app or a recovery code.",
+    "cs": "Vložte ověřovací kód z aplikace nebo kód pro obnovení přístupu."
+  },
+  "otp_hint": {
+    "en": "Enter 6 digits (verification code) or 16 alphanumeric characters (recovery code)",
+    "cs": "Zadejte 6 číslic (ověřovací kód) nebo 16 alfanumerických znaků (kód pro obnovení)"
   }
 }
diff --git a/themes/perun/privacyidea/LoginForm.php b/themes/perun/privacyidea/LoginForm.php
index 96e74382402f0040f0972241fcb897d56600c161..017fed9e821090de151c71fcb67199150911e5b2 100644
--- a/themes/perun/privacyidea/LoginForm.php
+++ b/themes/perun/privacyidea/LoginForm.php
@@ -109,6 +109,8 @@ if ($this->data['errorCode'] !== null) {
                     <label for="otp" class="sr-only"><?php echo $this->t('{privacyidea:privacyidea:otp}'); ?></label>
                     <input id="otp" name="otp" tabindex="1" value="" class="form-control" autocomplete="one-time-code"
                       inputmode="numeric" required
+                      pattern="\d{6}|\w{16}"
+                      title="<?php echo htmlspecialchars($this->t('{perun:privacyidea:otp_hint}'), ENT_QUOTES); ?>"
                       placeholder="<?php echo htmlspecialchars($otpHint, ENT_QUOTES); ?>"
                       <?php echo $this->data['noAlternatives'] ? ' autofocus' : '';?> />
                 </div>