Input OTP
Slots de código de un solo uso que admiten escritura, pegado y autocompletado de SMS, con una animación opcional que encadena los códigos en cascada y un estado para la verificación.
Type or paste 123456 to pass. Anything else fails.
pnpm dlx shadcn@latest add https://hextaui.com/r/input-otp.jsonAñade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.
Añade los tokens del tema a tu CSS global, si aún no lo has hecho.
Instala las dependencias.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopia y pega el siguiente código en tu proyecto.
components/ui/input-otp.tsx lib/motion.ts Actualiza las rutas de importación para que coincidan con la configuración de tu proyecto.
Renderiza un <InputOTPSlot /> por carácter y define length con el mismo número. Los slots encuentran su posición por sí solos, así que no hay una prop index que mantener sincronizada.
Unido
El aspecto por defecto. Cada <InputOTPGroup /> une sus slots en una sola tira con bordes compartidos.
Separado
variant="separate" da a cada slot su propia caja redondeada con un hueco entre ellas.
Tamaños
sm, default y lg coinciden con las alturas del input y del botón. En pantallas táctiles cada tamaño crece hasta al menos 44px con una fuente de 16px.
Animado
animated está desactivado por defecto. Con él, los caracteres escritos suben al entrar, los borrados se hunden al salir mientras el resto se desliza, y un código completo procedente de autocompletado, pegado o de tu propio estado se encadena en cascada slot a slot. Pulsa Fill code para ver la cascada.
Estado
status muestra el resultado de comprobar el código. loading bloquea los slots y marca el campo como ocupado, error marca todos los slots como no válidos y success vuelve verdes los bordes. Cada uno se anuncia. Con animated, loading ejecuta una onda, error sacude una vez y success hace aparecer los caracteres con un efecto pop.
Controlado
Pasa value y onValueChange. El valor es siempre el código filtrado, nunca más largo que length.
Form
Con un name, el código se envía con el formulario. autoSubmit envía en cuanto se llena el último slot, así que un código autocompletado inicia sesión sin otro toque.
Con Field
Dentro de un <Field /> la etiqueta, la descripción y el error se vinculan por ti. Introduce cualquier cosa menos 000000 para ver el error.
Inválido
aria-invalid en la raíz marca todos los slots. Vincula el mensaje con aria-describedby.
Letras y números
validationType="alphanumeric" acepta códigos de recuperación y de invitación, y normalizeValue los pasa a mayúsculas mientras se escriben o pegan.
Enmascarado
mask oculta cada carácter, para PIN. Desactiva el autocompletado con autoComplete="off" cuando el valor no sea un código de un solo uso.
Separador personalizado
Agrupa los slots como quieras y pasa tu propio icono a <InputOTPSeparator />.
Deshabilitado
Un campo deshabilitado no puede recibir foco ni editarse.
De derecha a izquierda
Los slots se llenan desde la derecha y las teclas de flecha siguen lo que ves. Dale a los slots posteriores al primero un aria-label traducido. Define dir="ltr" en el campo para mantener un código de izquierda a derecha en una página de derecha a izquierda.
| Key | Acción |
|---|---|
| Tab | Mueve el foco al campo, al primer slot vacío, y de nuevo fuera. Solo un slot está en el orden de tabulación. |
| ←→ | Pasa al slot anterior o siguiente, en orden visual en diseños de derecha a izquierda. |
| Home↑ | Pasa al primer slot. |
| End↓ | Pasa al slot posterior al último carácter. |
| Backspace | Borra el carácter del slot, o el anterior cuando el slot está vacío. Los caracteres posteriores retroceden. |
| Delete | Borra el carácter del slot y mantiene el foco allí. |
| CtrlBackspace | Borra todo el código. ⌘ Backspace en macOS. |
| CtrlA | Selecciona todo el código (⌘ A en macOS). Backspace o Delete lo borra entonces y vuelve al primer slot, escribir o pegar lo reemplaza, y Ctrl C copia todo. Cualquier otra tecla o un clic termina la selección. |
- Cada slot es un input real. El primero toma su nombre de tu
<label>oaria-label; los demás se llaman "Character 2 of 6" y así sucesivamente. Pasaaria-labelen un slot para traducirlo. - El primer slot tiene
autocomplete="one-time-code", así que iOS y macOS ofrecen códigos de Mensajes y Mail, Android ofrece códigos SMS y los gestores de contraseñas pueden rellenarlo. Un código completo que llega a un solo slot se reparte entre todos ellos. La cascada animada se ejecuta para cualquier origen, incluidos los códigos que definas desde la API WebOTP. - Siempre que el código queda vacío mientras un slot tiene foco, como tras borrar un código incorrecto, el foco vuelve al primer slot para que el siguiente intento empiece en el lugar correcto.
- Cuando se define
status, una región activa oculta junto al campo lo anuncia. Cambia las palabras conloadingLabel,successLabelyerrorLabel. - Con
animated, los caracteres se dibujan en una capa oculta para los lectores de pantalla mientras los inputs conservan el valor real. Con movimiento reducido, los caracteres solo se desvanecen y la onda de estado se convierte en un pulso suave.
Construido sobre el campo OTP de Base UI. Todas las props de Base UI se pasan.
| Prop | Tipo | Predeterminado |
|---|---|---|
lengthObligatorio. El número de slots; renderiza el mismo número de partes InputOTPSlot. | number | – |
variant | "joined" | "separate" | "joined" |
size | "sm" | "default" | "lg" | "default" |
animatedAnima la entrada y salida de caracteres, encadena en cascada la entrada de varios caracteres y anima el estado. | boolean | false |
statusEl resultado de comprobar el código. Loading deja los slots en solo lectura. | "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 | – |
onValueCompleteSe llama cuando se llena el último slot. | (value: string, details) => void | – |
onValueInvalidSe llama cuando se rechazan los caracteres escritos o pegados. | (value: string, details) => void | – |
validationType | "numeric" | "alpha" | "alphanumeric" | "none" | "numeric" |
normalizeValueSe ejecuta después de filtrar. Mantenla idempotente. | (value: string) => string | – |
inputModePor defecto proviene de validationType. | string | – |
autoComplete | string | "one-time-code" |
autoSubmit | boolean | false |
mask | boolean | false |
aria-invalidMarca todos los slots como no válidos. | boolean | – |
name | string | – |
form | string | – |
idVa en el primer slot, para que el htmlFor de una etiqueta apunte a él. | string | – |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Atributo | Descripción |
|---|---|
data-slot="input-otp" | La raíz. |
data-variant="joined" | "separate" | La variante actual. |
data-size | El tamaño actual. |
data-status | El estado, cuando hay uno definido. |
data-animated | Presente cuando animated está activado. |
data-shake | Presente mientras el campo se sacude tras pasar el estado a error. |
data-complete | Presente cuando todos los slots están llenos. |
data-filled | Presente cuando algún slot está lleno. |
data-focused | Presente mientras un slot tiene foco. |
data-disabled | Presente cuando está deshabilitado. |
data-readonly | Presente cuando es de solo lectura, incluso durante la carga. |
data-required | Presente cuando es obligatorio. |
data-invalid / data-valid / data-touched / data-dirty | Estado del campo, dentro de un Field. |
data-slot="input-otp-status" | La región activa oculta, hermana de la raíz. |
Un elemento simple que organiza una serie de slots. En la variante joined, sus slots comparten bordes.
| Prop | Tipo | Predeterminado |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <div> |
| Atributo | Descripción |
|---|---|
data-slot="input-otp-group" | Apunta al grupo en CSS. |
Una caja que contiene un input. className va en la caja; cualquier otra prop va en el input.
| Prop | Tipo | Predeterminado |
|---|---|---|
aria-labelSe ignora en el primer slot, que usa la etiqueta. | string | "Character N of M" |
classNameEl estado incluye el índice del slot, su valor, filled y el estado del campo. | string | (state) => string | – |
placeholder | string | – |
| Atributo | Descripción |
|---|---|
data-slot="input-otp-slot" | La caja. |
data-filled | Presente cuando el slot tiene un carácter. |
data-status | El estado de la raíz, cuando no es idle. |
--input-otp-index | La posición del slot, usada para escalonar el movimiento del estado. |
data-slot="input-otp-input" | El input de su interior, con los atributos data-filled, data-focused, data-complete y de campo de Base UI. |
data-slot="input-otp-char" | El carácter dibujado cuando animated está activado. |
Un separador con un icono de menos. Pasa children para usar otro icono.
| Prop | Tipo | Predeterminado |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
className | string | (state) => string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Atributo | Descripción |
|---|---|
data-slot="input-otp-separator" | Selecciona el separador en CSS. |
Los nombres de clase detrás de un slot y un grupo (inputOTPGroupVariants). Llámalos con { variant, size }.
- MotionLas curvas de easing, duraciones y la comprobación de movimiento reducido con las que se anima cada componente, además de hooks para transformaciones de tamaño y resaltados deslizantes.
- CalendarUna cuadrícula de fechas para selección única, de rango y múltiple, con meses deslizantes, vistas previas de rango y días de tamaño táctil.
- CheckboxUna casilla de verificación cuya marca se dibuja, con padres indeterminados, grupos y etiquetas que comparten su hover.
- ComboboxUn select filtrable con chips, grupos y resultados asíncronos, en un popup que cambia de tamaño mientras escribes.
- Date pickerUn botón que abre un calendario en un popover, o en una hoja inferior en móviles, para fechas únicas y rangos.
- FieldEtiquetas, descripciones y errores conectados a su control, con estados de validación y diseños para formularios.
Usado en bloques
Bloques que se construyen sobre Input OTP.
- ProfileLa sección Perfil de los ajustes de un producto de IA. Recorta una foto en un círculo, elige un nombre de usuario que se comprueba mientras escribes, confirma un nuevo correo con un código de 6 dígitos, añade enlaces que reconocen el sitio y mira una tarjeta en vivo de cómo te ven los demás.
- SecuritySesiones y seguridad para un producto de IA. Dispositivos activos con cierre de sesión que anima la salida de las filas, cambio de contraseña con un medidor de fortaleza en vivo, configuración de dos factores con un código QR real, una verificación de 6 dígitos y códigos de recuperación descargables, passkeys mediante WebAuthn y eliminación de la cuenta tras una confirmación escrita.