# OTPField

> El código de verificación: una casilla por dígito y un solo valor, con el autorrelleno del SMS.

```tsx
import { OTPField } from "sebs7n-ui/otp-field"
```

## Ejemplos

### Verificar un mail

`onValueComplete` avisa cuando entra el último dígito: en un código de seis cifras el botón "Continuar" sobra, porque no hay nada más que decidir.

```tsx
import { Field, FieldDescription, FieldLabel } from "sebs7n-ui/field"
import { OTPField } from "sebs7n-ui/otp-field"
import { useState } from "react"

function VerificarMail() {
  const [codigo, setCodigo] = useState("")
  const verificado = codigo === "482913"
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Field name="codigo">
        <FieldLabel>Código de verificación</FieldLabel>
        <OTPField onValueComplete={setCodigo} />
        <FieldDescription>Te lo mandamos a hola@acme.com. Probá con 482913.</FieldDescription>
      </Field>
      {codigo.length === 6 && (
        <p className={verificado ? "text-callout text-blue-900" : "text-callout text-red-900"}>
          {verificado ? "Mail verificado." : "Ese código no es el que mandamos."}
        </p>
      )}
    </div>
  )
}
```

### 2FA al entrar

El código viaja como un solo valor bajo el `name` del campo. Si está incompleto, el formulario no sale: lo valida el input escondido que tiene el código entero, no cada casilla por separado.

```tsx
import { Button } from "sebs7n-ui/button"
import { Field, FieldError, FieldLabel } from "sebs7n-ui/field"
import { Form } from "sebs7n-ui/form"
import { OTPField } from "sebs7n-ui/otp-field"
import { useState } from "react"

function DosFactores() {
  const [enviado, setEnviado] = useState("")
  return (
    <Form className="flex w-full max-w-sm flex-col gap-4" onFormSubmit={(valores) => setEnviado(String(valores.codigo))}>
      <Field name="codigo" validate={(valor) => (String(valor).length === 6 ? null : "Faltan dígitos: el código tiene seis.")}>
        <FieldLabel required>Código de tu app de autenticación</FieldLabel>
        <OTPField />
        <FieldError />
      </Field>
      <Button type="submit">Entrar</Button>
      {enviado && <p className="text-callout text-label-secondary">Se envió {enviado}.</p>}
    </Form>
  )
}
```

### Otros largos y tamaños

`length` cambia la cantidad de casillas; `size` usa las mismas alturas que `Input`, para que un OTP en medio de un formulario no desentone.

```tsx
import { OTPField } from "sebs7n-ui/otp-field"

function LargosYTamanos() {
  return (
    <div className="flex flex-col gap-4">
      <OTPField aria-label="Código de cuatro dígitos" length={4} size="sm" />
      <OTPField aria-label="Código de seis dígitos" />
      <OTPField aria-label="Código de ocho dígitos" length={8} size="lg" />
    </div>
  )
}
```

## Props

### OTPField

Hereda las props de `OTPField.Root`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `inputClassName` | `string` | — | Clases de cada casilla, para tocar el ancho o el tipo de letra sin reescribir el componente. |
| `length` | `number` | `6` | Cuántas casillas. Seis es lo que manda casi todo el mundo por SMS. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | `sm` 28 px · `md` 36 px · `lg` 40 px, las mismas alturas que `Input`. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

## Teclado

| Tecla | Qué hace |
|---|---|
| Tab | Entra y sale del campo entero. Adentro hay una sola casilla tabulable, la activa. |
| 0-9 | Escribe en la casilla y salta a la siguiente. |
| Backspace | Borra el dígito y retrocede. Con ⌘/Ctrl borra el código entero. |
| Delete | Borra el dígito sin moverse de casilla. |
| ← → | Se mueve entre casillas. Con ⌘/Ctrl va al principio o al final. |
| Home / End | Primera casilla · última casilla escrita. |
| ⌘V | Pega el código completo y lo reparte entre las casillas. |

## Accesibilidad

- El contenedor es un `role="group"` nombrado por el `FieldLabel`, y cada casilla hereda ese nombre: se anuncia un campo con nombre, no seis campos de texto anónimos.
- Debajo viaja un input oculto con el valor entero: es el que lleva el `name`, el `required` y el largo, así que la validación y el submit hablan del código, no de un dígito.
- Solo la casilla activa queda en el orden de tabulación: se entra y se sale con un Tab, no con seis.
- `autoComplete="one-time-code"` va en la primera casilla y en el input oculto: es lo que hace que iOS y Android ofrezcan el código del SMS. Pisarlo lo apaga.
- El error lo anuncia `FieldError` una sola vez, sobre el grupo. `aria-invalid` aparece cuando la invalidez la declara la app (`<Field invalid>` o un error del servidor); la que calcula el navegador pinta con `data-invalid`.

## Reglas de uso

- **Siempre dentro de un `Field` con `FieldLabel`.** Sin etiqueta, seis casillas son seis cajas sin nombre.
- **Seis casillas y `validationType="numeric"`** salvo que el proveedor mande otra cosa: es lo que la gente espera y lo que pone el teclado numérico en el celular.
- `onValueComplete` en lugar de un botón «Continuar»: cuando entra el último dígito no queda nada que decidir. Dejá el botón solo si el envío cuesta plata o es irreversible.
- `autoSubmit` manda el formulario solo al completarse. Úsalo cuando el error se puede reintentar sin costo; si no, el envío accidental es peor que un click de más.
- `mask` tapa los dígitos como una contraseña: casi nunca hace falta. Ver lo que se escribió es justamente lo que evita el segundo intento.
- No lo uses para un CUIT, una tarjeta ni un teléfono: esos son un `Input` con `inputMode`, porque se copian, se corrigen al medio y no tienen largo fijo de una cifra por casilla.

## Relacionados

[input](/docs/components/input.md) · [label](/docs/components/label.md) · [textarea](/docs/components/textarea.md)
