# React OTP Input: Einfügen, SMS-Autofill und Resend-Timer

> So baust du einen React OTP Input für Einfügen und SMS-Autofill, warum autocomplete one-time-code still scheitert und wie du zod und einen Resend-Timer ergänzt.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 9. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 11 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-otp-input
- Englische Fassung: https://wingo-ui.com/blog/react-otp-input

## Kurz gesagt

Ein React OTP Input setzt autocomplete="one-time-code" auf das Feld, das den Code empfängt, verzichtet auf maxLength={1}, verteilt einen eingefügten oder automatisch ausgefüllten Code auf alle Felder und leert ein Feld, bevor Backspace zurückspringt; der kostenlose OTP Input von Wingo UI erledigt all das. Android braucht noch ein weiteres Teil, die WebOTP API, denn Chrome dokumentiert SMS-Codes fürs Web über diese API statt über das Attribut. Für den Resend-Timer sperrst du den Button nach jedem Versand für eine feste Wartezeit, speicherst, wann sie endet, und setzt dieselbe Wartezeit auf dem Server durch.

Ein Screen für den Bestätigungscode sieht nach einem Nachmittag Arbeit aus: sechs Felder, der Fokus springt weiter, fertig. Die Fehler tauchen später auf, auf echten Smartphones. Sechs Eingabefelder mit je einem Zeichen machen aus dem Code, den das iPhone über der Tastatur anbietet, oder aus einem eingefügten Code eine einzelne Ziffer. Backspace in einem leeren Feld tut nichts, und der Link zum erneuten Senden feuert so schnell, wie jemand tippen kann. Dieser Guide zeigt, was ein React OTP Input können muss, warum SMS-Autofill ohne Fehlermeldung scheitert, und baut ein komplettes Bestätigungsformular mit react-hook-form, zod und einem Resend-Timer. Die Beispiele nutzen den kostenlosen [OTP Input](https://wingo-ui.com/components/otp-input) von Wingo UI, der Bibliothek, die wir bauen. Wir sind also nicht neutral; Fakten von außen verlinken auf ihre Quellen.

## Was muss ein React OTP Input können?

Er muss einen Code auf vier Wegen annehmen (Tippen, Einfügen, den Vorschlag der Tastatur aus einer SMS und auf Android die WebOTP API) und sich trotzdem wie ein einziges Feld bearbeiten lassen. Unsere Checkliste, und ein fairer Test für jede Bibliothek:

- **Tippen** springt ins nächste Feld, die Pfeiltasten wechseln zwischen den Feldern, und ein Tipp hinter das erste leere Feld landet in genau diesem Feld. So überspringt das Tippen nie eins.
- **Einfügen** füllt alle Felder auf einmal und behält nur die Zeichen, die sie annehmen. „123 456“ und „Dein Code lautet 123456“ ergeben also beide sechs Ziffern.
- **SMS-Autofill** braucht `autocomplete="one-time-code"` auf dem Feld, das den Code empfängt, und dort kein Limit auf ein Zeichen.
- **Backspace** leert das Feld, in dem du gerade bist. Der nächste Druck springt zurück und leert das Feld davor.
- **Die richtige Tastatur**: `inputMode="numeric"` für Ziffern, eine volle Tastatur mit Großbuchstaben für `type="letters"` oder `"both"`.
- **Feedback**: `status="error"` färbt die Felder rot und schüttelt die Reihe einmal, `"success"` zieht einen grünen Ring um jedes Feld, und reduzierte Bewegung schaltet die Animation ab.
- **Namen**: Jedes Feld wird als „Digit 1 of 6“ vorgelesen (`slotLabel` übersetzt das), die Reihe braucht ein Gruppen-Label, und der Fehler braucht Worte neben dem roten Ring.

Installier ihn und setz ihn in eine Client-Komponente:

```bash
npx wingo-ui@latest add otp-input
```

```tsx
"use client";

import { useState } from "react";
import OtpInput, { type OtpStatus } from "@/components/ui/otp-input";

export function CodeStep({ check }: { check: (code: string) => Promise<boolean> }) {
  const [status, setStatus] = useState<OtpStatus>("idle");

  return (
    <OtpInput
      role="group"
      aria-label="Bestätigungscode"
      length={6}
      autoFocus
      status={status}
      onChange={() => setStatus("idle")}
      onComplete={async (code) => setStatus((await check(code)) ? "success" : "error")}
    />
  );
}
```

`length` legt die Zahl der Felder fest (4 für eine PIN), `type` die erlaubten Zeichen, `mask` verbirgt sie wie ein Passwortfeld, und `size` wählt Felder mit 40, 48 oder 56px. Die Größe `sm` wächst auf Touchscreens auf 44px, und in einem Container, der schmaler als die Reihe ist, schrumpfen die Felder gleichmäßig und bleiben quadratisch. So passen auch acht Felder auf ein 360px breites Smartphone. Mehr zu Größen für den Daumen steht in [Mobile-first React Components](https://wingo-ui.com/de/blog/mobile-first-react-components).

> Live-Demo (OTP Input): Tipp 123456 für den grünen Erfolgsring oder einen anderen Code für das Schütteln beim Fehler. Leer das Feld danach und füg einen Code mit Leerzeichen ein, etwa 123 456. Probier sie unter [OTP Input](https://wingo-ui.com/components/otp-input) aus und installier sie mit `npx wingo-ui@latest add otp-input`.

## Wie funktioniert Autofill mit autocomplete one-time-code?

Das Attribut `autocomplete="one-time-code"` ist ein Hinweis an den Browser. Das Betriebssystem erkennt einen Code in einer eingehenden SMS, und wenn das fokussierte Feld dieses Token trägt, bietet die Tastatur den Code an. Ein Tipp fügt den ganzen Code auf einmal in dieses Feld ein. [MDN definiert das Token](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete) als Einmalpasswort (OTP), das die Identität bestätigt und bei der Anmeldung als zusätzlicher Faktor dient.

Ob autocomplete one-time-code greift, hängt von der Plattform ab. Laut [dem Guide von web.dev zu SMS-OTP-Formularen](https://web.dev/articles/sms-otp-form) (zuletzt im Dezember 2020 aktualisiert und bei unserer Prüfung im Oktober 2026 noch die aktuelle Fassung) funktioniert das Attribut in Safari ab Version 12 auf iOS, iPadOS und macOS. Chrome, Opera und Vivaldi auf Android unterstützen an den Origin gebundene Codes dagegen über die WebOTP API und ausdrücklich nicht über `autocomplete="one-time-code"`.

Seit iOS 14 und macOS Big Sur kannst du einen Code bei Apple auch an deine Domain binden. Laut [Apples Doku zu domaingebundenen Codes](https://developer.apple.com/documentation/security/enabling-autofill-for-domain-bound-sms-codes) steht in der letzten Zeile der Nachricht deine Domain nach einem `@` und der Code nach einem `#`, getrennt durch ein Leerzeichen. AutoFill schlägt einen domaingebundenen Code nur vor, wenn die in Safari geöffnete Seite zu dieser Domain passt:

```text
Dein Example-Code lautet 123456.

@example.com #123456
```

Die WebOTP API von Chrome liest dieselbe letzte Zeile, also bedient ein Nachrichtenformat beide Plattformen.

Der OTP Input setzt das Token auf sein erstes Feld und alle anderen auf `off`. Seine Felder haben kein `maxLength`, deshalb kommt ein eingefügter Code in einem einzigen Change-Event an. Die Komponente behandelt ihn wie einen eingefügten Text und verteilt ihn auf die Felder. Ein Tipp auf eine leere Reihe landet immer im ersten Feld, so beginnt ein automatisch ausgefüllter Code an der richtigen Stelle.

## Warum scheitert React OTP Input Autofill ohne Fehlermeldung?

Autofill scheitert still, weil eine verfehlte Bedingung kein Fehler ist: Die Tastatur zeigt keinen Vorschlag, und in der Konsole steht nichts. Die Ursache liegt im Markup, in der Nachricht oder im Testaufbau. Das prüfen wir, in dieser Reihenfolge:

1. **Jedes Feld hat `maxLength={1}`.** Das Limit kürzt alles, was jemand einfügt, auch den Tipp auf den vorgeschlagenen Code. Nur die erste Ziffer kommt an. Entferne das Limit und filtere im Change-Handler.
2. **Das Token fehlt oder wird überschrieben.** Ein Wrapper, der `autoComplete` nicht weiterreicht, oder ein `autocomplete="off"`, das jemand gegen ein anderes Autofill eingebaut hat. Laut MDN darf der Browser bei `off` für dieses Feld keinen Wert automatisch eintragen oder auswählen.
3. **Die Nachricht ist an eine andere Domain gebunden.** Ein Code, der auf `@example.com #123456` endet, wird auf einem Preview-Deployment oder einem Staging-Host unter einer anderen Domain nicht angeboten. Mach die Domain in der Nachricht zu einer Einstellung pro Umgebung.
4. **Du testest auf Android nur mit dem Attribut.** Chrome dokumentiert dafür die WebOTP API; mehr dazu im nächsten Abschnitt.
5. **Der Listener startet zu spät, oder die Seite läuft über HTTP.** Laut [Chromes WebOTP-Guide](https://developer.chrome.com/docs/identity/web-apis/web-otp) muss die Nachricht gesendet werden, nachdem `navigator.credentials.get()` aufgerufen wurde. Zeig den Code-Schritt also, sobald die Sendeanfrage startet, nicht erst nach einer Weiterleitung. Die API gibt es außerdem nur in sicheren Kontexten. Wenn du deinen Dev-Server vom Smartphone unter `http://192.168.1.20:3000` öffnest, ist die Feature-Prüfung false. Nutze einen HTTPS-Tunnel.
6. **Der Code passt nicht in die Felder.** Ein Code mit 8 Zeichen in einem Input mit 6 Feldern verliert seine letzten zwei Zeichen, und ein Code mit Buchstaben verliert die Buchstaben, wenn `type` auf `"numbers"` steht. Stimm `length` und `type` auf das ab, was dein Server sendet.
7. **Du hast im Emulator oder in einem Headless-Browser getestet.** Das [README von input-otp](https://github.com/guilhermerodz/input-otp) weist darauf hin, dass sich SMS-Autofill nicht headless testen lässt. Teste auf einem echten Smartphone mit einer echten Nachricht.

## Wie lese ich den Code auf Android mit der WebOTP API?

Ruf `navigator.credentials.get({ otp: { transport: ["sms"] } })` auf, sobald der Code-Screen gemountet wird, brich den Aufruf beim Unmount ab und schreib den Code in den Wert des Inputs. Kommt eine passende Nachricht an, bittet Chrome die Person in einem Bottom Sheet um Bestätigung und löst das Promise dann mit dem Code auf. Prüf vorher, ob es die API gibt: MDN kennzeichnet sie als experimentell und nicht Baseline, und Safari setzt stattdessen auf das Attribut. Speicher das als `hooks/use-web-otp.ts`:

```tsx
import { useEffect, useEffectEvent } from "react";

type OtpCredential = Credential & { code: string };

// Android Chrome: wartet auf eine SMS, deren letzte Zeile "@deine-domain #123456" lautet, und gibt den Code weiter.
// Vor dem Versand mounten; `attempt` erhöhen, um nach erneutem Senden wieder zu lauschen
export function useWebOtp(onCode: (code: string) => void, attempt = 0) {
  const receive = useEffectEvent(onCode);

  useEffect(() => {
    if (!("OTPCredential" in window)) return;
    const controller = new AbortController();
    navigator.credentials
      .get({ otp: { transport: ["sms"] }, signal: controller.signal } as CredentialRequestOptions)
      .then((credential) => {
        const code = (credential as OtpCredential | null)?.code;
        if (code) receive(code);
      })
      .catch(() => {
        // abgebrochen, weggeklickt oder keine Nachricht: Tippen geht trotzdem
      });
    return () => controller.abort();
  }, [attempt]);
}
```

Der Cast ist nötig, weil den DOM-Typen von TypeScript die Option `otp` fehlt, und `useEffectEvent` braucht React 19.2, das der App Router von Next.js 16 nutzt. Jeder Aufruf von `get()` löst nur einmal auf, deshalb startet `attempt` den Listener nach einem erneuten Senden neu. In einem Cross-Origin-iframe braucht die API außerdem die Berechtigung `otp-credentials`, so steht es auf [der WebOTP-Seite von MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API).

## Wie validiere ich einen OTP Input mit React Hook Form und zod?

Pack ihn in `Controller` und validiere den zusammengesetzten String mit zod. Ein React Hook Form OTP Input braucht `Controller`, weil `register` native Inputs über eine `ref` liest und zurücksetzt, der OTP Input aber eine Reihe von Inputs mit einem gemeinsamen String-Wert ist. Für Label, Hinweis und Fehler setzt du ihn in ein [Field](https://wingo-ui.com/components/field) mit `labelAs="span"` und lässt `FieldControl` die IDs an die Reihe weitergeben: Die Gruppe bekommt `aria-labelledby` und `aria-describedby`, und ein Klick auf das Label fokussiert das erste Feld.

Der komplette Bestätigungsschritt nutzt den WebOTP-Hook von oben, einen Resend-Timer und einen [Button](https://wingo-ui.com/components/button) für jede Aktion. Installier, was er importiert (useCooldown ist ein Pro-Item):

```bash
npm install react-hook-form @hookform/resolvers zod
npx wingo-ui@latest add otp-input field button use-cooldown
```

Die Komponente bekommt `verify` und `resend` als Props, so kann eine Next.js-Seite zwei Server Actions übergeben:

```tsx
"use client";

import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Field, FieldControl } from "@/components/ui/field";
import OtpInput, { type OtpStatus } from "@/components/ui/otp-input";
import { useCooldown } from "@/hooks/use-cooldown";
import { useWebOtp } from "@/hooks/use-web-otp";

const schema = z.object({
  code: z.string().regex(/^\d{6}$/, "Gib den 6-stelligen Code ein."),
});

type Props = {
  phone: string;
  verify: (code: string) => Promise<boolean>;
  resend: () => Promise<void>;
};

export function VerifyCode({ phone, verify, resend }: Props) {
  const [status, setStatus] = useState<OtpStatus>("idle");
  const [sent, setSent] = useState(0);
  const cooldown = useCooldown({ seconds: 30, autoStart: true, persistKey: "verify-resend" });
  const form = useForm({
    resolver: zodResolver(schema),
    defaultValues: { code: "" },
    reValidateMode: "onSubmit",
  });

  const submit = form.handleSubmit(async ({ code }) => {
    if (await verify(code)) {
      setStatus("success");
      return;
    }
    setStatus("error");
    form.setValue("code", "");
    form.setError("code", { message: "Dieser Code hat nicht funktioniert. Sieh in der neuesten SMS nach." });
    document.querySelector<HTMLInputElement>("#verify-code input")?.focus();
  });

  // Android Chrome: Der Code kommt über WebOTP, füllt die Felder und sendet ab
  useWebOtp((code) => {
    form.setValue("code", code);
    void submit();
  }, sent);

  return (
    <form noValidate onSubmit={submit} className="flex flex-col gap-4">
      <Controller
        control={form.control}
        name="code"
        render={({ field, fieldState }) => (
          <Field
            id="verify-code"
            label="Bestätigungscode"
            labelAs="span"
            description={`Wir haben einen 6-stelligen Code an ${phone} gesendet.`}
            error={fieldState.error?.message}
          >
            <FieldControl>
              <OtpInput
                role="group"
                autoFocus
                value={field.value}
                status={status}
                onChange={(code) => {
                  field.onChange(code);
                  setStatus("idle");
                  form.clearErrors("code");
                }}
                // die letzte Ziffer sendet ab, ob getippt, eingefügt oder automatisch ausgefüllt
                onComplete={() => {
                  if (!form.formState.isSubmitting) void submit();
                }}
              />
            </FieldControl>
          </Field>
        )}
      />
      <Button type="submit" fullWidth loading={form.formState.isSubmitting}>
        Bestätigen
      </Button>
      <Button
        type="button"
        variant="ghost"
        fullWidth
        disabled={cooldown.active}
        onClick={() => {
          // die Wartezeit startet vor der Anfrage, so kann ein doppelter Tipp keine zwei Nachrichten senden
          cooldown.start();
          setSent((count) => count + 1);
          void resend();
        }}
      >
        <span className="tabular-nums">
          {cooldown.active ? `Code erneut senden in ${cooldown.label}` : "Code erneut senden"}
        </span>
      </Button>
    </form>
  );
}
```

Drei Details, die du übernehmen solltest:

- **`reValidateMode: "onSubmit"`** verhindert, dass „Gib den 6-stelligen Code ein.“ beim erneuten Tippen aufblitzt, und `clearErrors` in `onChange` blendet die alte Meldung beim ersten Tastendruck aus.
- **Ein falscher Code leert die Felder und fokussiert das erste.** `field.ref` würde auf dem `div` landen, das die Reihe umschließt und keinen Fokus annehmen kann. Deshalb sucht das Beispiel das erste Feld über die ID des Field.
- **Ohne React Hook Form**, in einem Formular, das an eine Server Action sendet, renderst du `<input type="hidden" name="code" value={code} />` neben der Reihe. Die Felder haben kein `name`, also würde `FormData` sie nicht sehen.

## Wie baue ich einen Resend-Timer in einen OTP-Screen?

Sperr den Button zum erneuten Senden nach jedem Versand für eine feste Wartezeit, und speicher, wann die Wartezeit endet, statt eine Zahl herunterzuzählen. Ein Zähler mit `setInterval` driftet, wenn das Smartphone sperrt oder der Tab schläft, und ein Neuladen gibt einen frischen Button heraus. Der [Hook useCooldown](https://wingo-ui.com/components/use-cooldown) merkt sich den Endzeitpunkt, tickt einmal pro Sekunde passend dazu, übersteht mit `persistKey` ein Neuladen und öffnet den Screen mit `autoStart` schon in der Wartezeit, weil der erste Code ja gerade gesendet wurde.

> Live-Demo (useCooldown): Drück auf Code erneut senden: Der Button sperrt sich und zählt von 0:30 herunter, während sich der Ring füllt, dann wird er von selbst wieder aktiv. Probier sie unter [useCooldown](https://wingo-ui.com/components/use-cooldown) aus und installier sie mit `npx wingo-ui@latest add use-cooldown`.

Der Hook liefert `remaining`, `active`, `progress` für einen Ring und ein `label` wie „0:42“ für den Button. Der Button schützt nur ehrliche Nutzer: Jeder kann deinen Sende-Endpunkt direkt aufrufen. Setz deshalb dieselbe Wartezeit und ein Tageslimit auf dem Server durch, pro Telefonnummer und IP-Adresse. useCooldown gehört zu Wingo UI Pro. Wenn du deinen eigenen Hook schreibst, halte den End-Zeitstempel im State und berechne die restlichen Sekunden bei jedem Tick aus `Date.now()`.

## Solltest du stattdessen den shadcn OTP Input nutzen?

Nimm den [shadcn OTP Input](https://ui.shadcn.com/docs/components/input-otp), wenn du schon shadcn/ui nutzt, ein Trennzeichen zwischen zwei Dreiergruppen willst oder den Code in einem echten Input haben möchtest. Nimm den OTP Input von Wingo UI für eingebaute Erfolgs- und Fehlerzustände, Größen für Touch und ein einziges Element, das du über Props konfigurierst. Die shadcn-Version basiert auf input-otp: Es rendert einen Text-Input, macht ihn unsichtbar und zeichnet die Felder aus seinem State. Unsere Komponente rendert einen Input pro Feld. Beide verarbeiten Einfügen und SMS-Autofill. Stand Oktober 2026:

| | shadcn/ui Input OTP | Wingo UI OTP Input |
| --- | --- | --- |
| Unter der Haube | input-otp, ein unsichtbarer Input | Ein Input pro Feld |
| API | Zusammengesetzt: `InputOTP`, `InputOTPGroup`, `InputOTPSlot`, `InputOTPSeparator` | Ein Element mit Props (`length`, `type`, `size`, `status`, `mask`, Farben) |
| Erlaubte Zeichen | Regex in `pattern`, etwa `REGEXP_ONLY_DIGITS` | `type`: numbers, letters oder both |
| Trennzeichen zwischen Gruppen | Ja, `InputOTPSeparator` | Nein |
| Formularwert | Ein Input mit `name`, `FormData` sieht also den Code | Kein `name` auf den Feldern; ergänze einen versteckten Input |
| Feedback-Zustände | Fehlerstil über `aria-invalid` auf den Feldern | Roter Ring und Schütteln bei Fehlern, grüner Ring bei Erfolg, reduzierte Bewegung wird respektiert |
| Installation | `npx shadcn@latest add input-otp` | `npx wingo-ui@latest add otp-input` |

Das [README von input-otp](https://github.com/guilhermerodz/input-otp) zählt auch auf, was ein einzelner Input von einem nativen Textfeld behält: Rückgängig, Alles auswählen, teilweises Einfügen in die Mitte eines halb ausgefüllten Codes und Platz für die Badges von Passwortmanagern. Unsere Felder verarbeiten Einfügen, Backspace und die Pfeiltasten im Code, aber Rückgängig und Alles auswählen enden an einem einzelnen Feld.

## Wie füge ich den OTP Input in ein Next.js-Projekt ein?

Führ den Add-Befehl in einem Next.js-Projekt mit Tailwind CSS v4 aus. Die CLI kopiert die Quelldateien in dein Projekt und installiert die npm-Pakete, die sie importieren:

```bash
npx wingo-ui@latest add otp-input field button
```

Die [Doku zum OTP Input](https://wingo-ui.com/components/otp-input) enthält den Playground und alle Props. Wenn du lieber mit einem fertigen Screen startest: Der Block [Login Page](https://wingo-ui.com/components/login-page) bringt diesen Ablauf in einer Anmeldeseite mit, mit einer Code-Ansicht, einem Resend-Timer und einer Sperre von 60 Sekunden nach drei falschen Codes. Login Page und useCooldown gehören zu Wingo UI Pro. Pro kostet Stand Oktober 2026 $8 im Monat, $80 im Jahr oder einmalig $150 ([Preise von Wingo UI](https://wingo-ui.com/de/pricing)). Für den Rest eines Formulars lies den [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide); weitere Guides findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components).

## Komponenten in diesem Artikel

- [OTP Input](https://wingo-ui.com/components/otp-input) (Free): A React OTP input for one-time verification codes and PINs, with digits that roll into place and a caret that slides between slots. Installation: `npx wingo-ui@latest add otp-input`
- [useCooldown](https://wingo-ui.com/components/use-cooldown) (Pro): A React cooldown timer hook for resend and retry buttons: wait N seconds before a code can be sent again, kept across reloads. Installation: `npx wingo-ui@latest add use-cooldown`
- [Field](https://wingo-ui.com/components/field) (Free): A React form field wrapper every control shares: label, hint, animated error and counter, fieldsets, and the box recipe all inputs use. Installation: `npx wingo-ui@latest add field`

## Häufige Fragen

### Wie fülle ich in React einen OTP-Code aus der SMS automatisch aus?

Setz autocomplete="one-time-code" auf das Eingabefeld, das den Code empfängt, verzichte auf maxLength={1} und beende die SMS mit einer Zeile wie „@example.com #123456“. Ruf auf Android zusätzlich die WebOTP API auf, denn Chrome dokumentiert an den Origin gebundene Codes über diese API statt über das Attribut.

### Warum füllt OTP-Autofill nur das erste Feld?

Jedes Feld hat ein maxLength von 1, also kürzt der Browser den eingefügten Code auf ein Zeichen, bevor dein Change-Handler läuft. Entferne das Limit, filtere die Zeichen selbst und verteile eine Änderung mit mehreren Zeichen auf die Felder, genau wie beim Einfügen.

### Funktioniert autocomplete one-time-code in Chrome auf Android?

Verlass dich nicht darauf. web.dev nennt das Attribut für Safari auf iOS, iPadOS und macOS und führt Chrome, Opera und Vivaldi auf Android stattdessen unter der WebOTP API. Behalte das Attribut für Safari und ergänze für Android einen Aufruf von navigator.credentials.get mit dem Transport sms.

### Wie nutze ich einen OTP Input mit react-hook-form?

Pack ihn in Controller, gib field.value und field.onChange weiter und ruf handleSubmit in onComplete auf, damit die letzte Ziffer das Formular absendet. Validiere mit einer zod-Regel wie einem Regex für sechs Ziffern und entferne den Fehler, sobald die Person wieder tippt.

### Ist der OTP Input von Wingo UI kostenlos?

Ja. OTP Input, Field und Button sind kostenlos und lassen sich mit npx wingo-ui@latest add otp-input field button installieren. Der Hook useCooldown und der Block Login Page gehören zu Wingo UI Pro.

---

Quelle: https://wingo-ui.com/de/blog/react-otp-input
