Input OTP
Des emplacements pour code à usage unique qui acceptent la saisie, le collage et le remplissage automatique par SMS, avec une animation facultative qui fait cascader les codes et un statut de vérification.
Type or paste 123456 to pass. Anything else fails.
pnpm dlx shadcn@latest add https://hextaui.com/r/input-otp.jsonAjoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.
Ajoutez les tokens de thème à votre CSS global, si ce n’est pas déjà fait.
Installez les dépendances.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopiez et collez le code suivant dans votre projet.
components/ui/input-otp.tsx lib/motion.ts Mettez à jour les chemins d’import selon la configuration de votre projet.
Rendez un <InputOTPSlot /> par caractère et définissez length au même nombre. Les emplacements trouvent seuls leur position : il n’y a donc pas de prop index à garder synchronisée.
Joint
L’apparence par défaut. Chaque <InputOTPGroup /> réunit ses emplacements en une seule bande aux bords partagés.
Séparé
variant="separate" donne à chaque emplacement sa propre boîte arrondie avec un écart entre elles.
Tailles
sm, default et lg correspondent aux hauteurs du champ et du bouton. Sur écran tactile, chaque taille passe à au moins 44 px avec une police de 16 px.
Animé
animated est désactivé par défaut. Activé, les caractères saisis montent en entrant, ceux supprimés descendent en sortant pendant que les autres glissent, et un code entier venant du remplissage automatique, du collage ou de votre propre état cascade emplacement par emplacement. Appuyez sur Fill code pour voir la cascade.
Statut
status affiche le résultat de la vérification du code. loading verrouille les emplacements et marque le champ comme occupé, error marque chaque emplacement comme invalide et success colore les bords en vert. Chacun est annoncé. Avec animated, loading lance une vague, error vibre une fois et success fait rebondir les caractères.
Contrôlé
Passez value et onValueChange. La valeur est toujours le code filtré, jamais plus long que length.
Form
Avec un name, le code est soumis avec le formulaire. autoSubmit soumet dès que le dernier emplacement est rempli : un code rempli automatiquement connecte donc l’utilisateur sans un appui de plus.
Avec Field
Dans un <Field />, le libellé, la description et l’erreur sont reliés pour vous. Saisissez n’importe quoi sauf 000000 pour voir l’erreur.
Invalide
aria-invalid sur la racine marque chaque emplacement. Reliez le message avec aria-describedby.
Lettres et chiffres
validationType="alphanumeric" accepte les codes de récupération et d’invitation, et normalizeValue les met en majuscules au fur et à mesure de la saisie ou du collage.
Masqué
mask masque chaque caractère, pour les codes PIN. Désactivez le remplissage automatique avec autoComplete="off" lorsque la valeur n’est pas un code à usage unique.
Séparateur personnalisé
Groupez les emplacements comme vous voulez et passez votre propre icône à <InputOTPSeparator />.
Désactivé
Un champ désactivé ne peut ni recevoir le focus ni être modifié.
De droite à gauche
Les emplacements se remplissent depuis la droite et les flèches suivent ce que vous voyez. Donnez aux emplacements après le premier un aria-label traduit. Définissez dir="ltr" sur le champ pour garder un code de gauche à droite dans une page de droite à gauche.
| Touche | Action |
|---|---|
| Tab | Déplace le focus dans le champ, vers le premier emplacement vide, et en dehors. Un seul emplacement est dans l’ordre de tabulation. |
| ←→ | Passe à l’emplacement précédent ou suivant, dans l’ordre visuel dans les mises en page de droite à gauche. |
| Home↑ | Passe au premier emplacement. |
| End↓ | Passe à l’emplacement après le dernier caractère. |
| Backspace | Supprime le caractère de l’emplacement, ou celui d’avant lorsque l’emplacement est vide. Les caractères suivants reculent. |
| Delete | Supprime le caractère de l’emplacement et y garde le focus. |
| CtrlBackspace | Efface tout le code. ⌘ Backspace sur macOS. |
| CtrlA | Sélectionne tout le code (⌘ A sur macOS). Backspace ou Delete l’efface alors et revient au premier emplacement, saisir ou coller le remplace, et Ctrl C le copie en entier. Toute autre touche ou un clic met fin à la sélection. |
- Chaque emplacement est un vrai champ. Le premier tire son nom de votre
<label>ou dearia-label; les autres sont nommés « Character 2 of 6 » et ainsi de suite. Passezaria-labelsur un emplacement pour le traduire. - Le premier emplacement a
autocomplete="one-time-code": iOS et macOS proposent donc des codes issus de Messages et Mail, Android propose les codes SMS, et les gestionnaires de mots de passe peuvent le remplir. Un code entier arrivant dans un seul emplacement est réparti sur tous. La cascade animée s’exécute pour toutes les sources, y compris les codes que vous définissez depuis l’API WebOTP. - Chaque fois que le code devient vide alors qu’un emplacement a le focus, par exemple après l’effacement d’un mauvais code, le focus revient au premier emplacement pour que l’essai suivant démarre au bon endroit.
- Lorsque
statusest défini, une région live masquée à côté du champ l’annonce. Modifiez les mots avecloadingLabel,successLabeleterrorLabel. - Avec
animated, les caractères sont dessinés sur une couche masquée aux lecteurs d’écran tandis que les champs gardent la vraie valeur. Avec la réduction des animations, les caractères ne font que se fondre et la vague de statut devient une douce pulsation.
Construit sur le champ OTP de Base UI. Toutes les props de Base UI sont transmises.
| Prop | Type | Par défaut |
|---|---|---|
lengthRequis. Le nombre d’emplacements ; rendez le même nombre de parties InputOTPSlot. | number | – |
variant | "joined" | "separate" | "joined" |
size | "sm" | "default" | "lg" | "default" |
animatedAnime l’entrée et la sortie des caractères, fait cascader la saisie de plusieurs caractères et anime le statut. | boolean | false |
statusLe résultat de la vérification du code. Le chargement met les emplacements en lecture seule. | "idle" | "loading" | "success" | "error" | – |
loadingLabel | string | "Verifying code" |
successLabel | string | "Code verified" |
errorLabel | string | "Code is incorrect" |
value | string | – |
defaultValue | string | – |
onValueChange | (value: string, details) => void | – |
onValueCompleteAppelé lorsque le dernier emplacement est rempli. | (value: string, details) => void | – |
onValueInvalidAppelé lorsque des caractères saisis ou collés sont rejetés. | (value: string, details) => void | – |
validationType | "numeric" | "alpha" | "alphanumeric" | "none" | "numeric" |
normalizeValueS’exécute après le filtrage. Gardez-le idempotent. | (value: string) => string | – |
inputModePar défaut, d’après validationType. | string | – |
autoComplete | string | "one-time-code" |
autoSubmit | boolean | false |
mask | boolean | false |
aria-invalidMarque chaque emplacement comme invalide. | boolean | – |
name | string | – |
form | string | – |
idSe place sur le premier emplacement, pour que le htmlFor d’un libellé le cible. | string | – |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribut | Description |
|---|---|
data-slot="input-otp" | La racine. |
data-variant="joined" | "separate" | La variante actuelle. |
data-size | La taille actuelle. |
data-status | Le statut, lorsqu’il est défini. |
data-animated | Présent lorsque animated est activé. |
data-shake | Présent pendant que le champ vibre après le passage du statut à error. |
data-complete | Présent lorsque chaque emplacement est rempli. |
data-filled | Présent lorsqu’un emplacement est rempli. |
data-focused | Présent tant qu’un emplacement a le focus. |
data-disabled | Présent lorsque l’élément est désactivé. |
data-readonly | Présent en lecture seule, y compris pendant le chargement. |
data-required | Présent lorsqu’elle est requise. |
data-invalid / data-valid / data-touched / data-dirty | État du champ, dans un Field. |
data-slot="input-otp-status" | La région live masquée, sœur de la racine. |
Un élément simple qui dispose une série d’emplacements. Dans la variante joined, ses emplacements partagent leurs bords.
| Prop | Type | Par défaut |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribut | Description |
|---|---|
data-slot="input-otp-group" | Ciblez le groupe en CSS. |
Une boîte contenant un champ. className va sur la boîte ; toutes les autres props vont sur le champ.
| Prop | Type | Par défaut |
|---|---|---|
aria-labelIgnoré sur le premier emplacement, qui utilise le libellé. | string | "Character N of M" |
classNameL’état contient l’index de l’emplacement, sa valeur, filled et l’état du champ. | string | (state) => string | – |
placeholder | string | – |
| Attribut | Description |
|---|---|
data-slot="input-otp-slot" | La boîte. |
data-filled | Présent lorsque l’emplacement contient un caractère. |
data-status | Le statut de la racine, lorsqu’il n’est pas idle. |
--input-otp-index | La position de l’emplacement, utilisée pour échelonner le mouvement du statut. |
data-slot="input-otp-input" | Le champ à l’intérieur, avec les attributs data-filled, data-focused, data-complete et field de Base UI. |
data-slot="input-otp-char" | Le caractère dessiné lorsque animated est activé. |
Un séparateur avec une icône moins. Passez des enfants pour utiliser une autre icône.
| Prop | Type | Par défaut |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribut | Description |
|---|---|
data-slot="input-otp-separator" | Ciblez le séparateur en CSS. |
Les noms de classes derrière un emplacement et un groupe (inputOTPGroupVariants). Appelez-les avec { variant, size }.
- MotionLes courbes d’easing, les durées et la vérification de réduction des animations utilisées par chaque composant, ainsi que des hooks pour les morphs de taille et les surlignages glissants.
- CalendarUne grille de dates pour la sélection simple, de plage et multiple, avec des mois qui défilent, des aperçus de plage et des jours à taille tactile.
- CheckboxUne case à cocher dont la coche se dessine, avec des parents indéterminés, des groupes et des libellés qui partagent son survol.
- ComboboxUn select filtrable avec chips, groupes et résultats asynchrones, dans un popup qui se redimensionne pendant la saisie.
- Date pickerUn bouton qui ouvre un calendrier dans un popover, ou une bottom sheet sur mobile, pour des dates simples et des plages.
- FieldDes libellés, descriptions et erreurs reliés à leur contrôle, avec états de validation et mises en page pour les formulaires.
Utilisé dans les blocks
Des blocks qui s’appuient sur Input OTP.
- ProfileLa section Profil des paramètres d’un produit d’IA. Recadrez une photo en cercle, choisissez un nom d’utilisateur vérifié pendant la saisie, confirmez une nouvelle adresse e-mail avec un code à 6 chiffres, ajoutez des liens qui reconnaissent le site et voyez une carte en direct de ce que les autres voient de vous.
- SecuritySessions et sécurité pour un produit d’IA. Appareils actifs avec déconnexion qui fait disparaître les lignes en animation, changement de mot de passe avec jauge de robustesse en direct, configuration de l’authentification à deux facteurs avec un vrai code QR, une vérification à 6 chiffres et des codes de récupération téléchargeables, des passkeys via WebAuthn, et la suppression du compte derrière une confirmation saisie.