Skip to content
This page has been auto-translated and may contain errors.View in English

Validación de formularios

docs.scrimba.com

Un formulario recopila información de las personas, y las personas dejan campos en blanco, escriben mal un correo electrónico o ponen letras donde debería ir un número. No quieres que esos datos lleguen a tu código sin cambios. Antes de que se envíe algo, el navegador puede verificar mucho por ti, pero solo si describes qué espera cada campo. Esa descripción se escribe directamente en tu HTML, y este capítulo trata sobre cómo escribirla.

Campos requeridos y tipos de entrada

Las dos verificaciones más económicas que puedes agregar son decir que un campo debe completarse en absoluto y decir qué tipo de valor va en él.

Agrega la palabra required a una entrada y el navegador se rehúsa a enviar el formulario hasta que ese campo tenga algo en él. No escribes ningún código para que esto suceda. El atributo es la instrucción.

html
<input type="text" name="full_name" required>

La otra mitad es el atributo type. Le dice al navegador qué forma debe tener el valor, y el navegador lo verifica por ti. Usa type="email" y el navegador se asegura de que el valor parezca una dirección de correo electrónico. Usa type="number" y solo acepta números. Hay un tipo para fechas, uno para direcciones web y más.

Piénsalo como un formulario en una clínica donde algunas casillas están marcadas como "debes completar" y la casilla de fecha ya tiene pequeños espacios para día, mes y año. El papel te está diciendo qué va dónde antes de que escribas algo.

required es un atributo booleano: su sola presencia activa la regla y no toma ningún valor. Un control marcado como required bloquea el envío del formulario mientras esté vacío.

El atributo type tiene doble función. Elige el control que renderiza el navegador (un selector de fecha, un botón de número) y establece una restricción integrada en el valor:

  • type="email" requiere una @ y una cola con forma de dominio.
  • type="url" requiere algo que se analice como una dirección web absoluta.
  • type="number" acepta solo entrada numérica y desbloquea min, max y step.
  • type="tel" no restringe el valor, pero muestra un teclado telefónico en dispositivos móviles.

Busca el tipo específico antes de buscar cualquier otra cosa. Es el código más pequeño para la mayor cantidad de verificación, y mejora el teclado en pantalla que la gente obtiene en dispositivos táctiles.

required y type son las dos primeras capas de lo que la plataforma llama validación de restricción: el sistema integrado del navegador para verificar el valor de un control contra las reglas que declaras en el marcado. required establece la regla "valor faltante". type establece una regla de formato vinculada a ese tipo.

Dos notas de producción en type. Primero, type="email" no valida contra la especificación completa de correo electrónico. Los navegadores usan un patrón deliberadamente permisivo (una secuencia de caracteres, una @, luego un dominio separado por puntos) porque una verificación más estricta rechazaría direcciones que son realmente entregables. Trátalo como una verificación de forma, no como prueba de que la dirección existe. Segundo, un type desconocido o no compatible vuelve a type="text", por lo que un campo nunca se vuelve inutilizable en un navegador más antiguo, solo pierde la verificación adicional. Ese retroceso elegante es por qué puedes adoptar tipos más nuevos sin tener una tabla de compatibilidad a mano.

El tipo también controla el teclado en pantalla y, a través del atributo inputmode relacionado (una sugerencia sobre qué diseño de teclado mostrar sin cambiar la validación), puedes ajustar ese teclado de forma independiente. Obtener el tipo correcto es una decisión de accesibilidad y ergonomía tanto como de validación.

Aquí hay un pequeño formulario de registro usando ambos atributos:

html
<form>
  <label>
    Dirección de correo electrónico
    <input type="email" name="email" required>
  </label>
  <label>
    Sitio web
    <input type="url" name="website">
  </label>
  <button>Registrarse</button>
</form>

El campo de correo electrónico debe completarse y debe parecer un correo electrónico. El campo de sitio web es opcional, pero si alguien escribe algo, tiene que parecer una dirección web. El navegador verifica ambos en el momento en que se presiona el botón.

JunoCampos requeridos y tipos de entrada Dos palabras pequeñas hacen mucho trabajo aquí. Pon required en un campo y el navegador no dejará pasar uno vacío. Establece el type, como email o number, y verifica que el valor tenga la forma correcta. Elige el tipo que coincida con el campo y obtendrás verificación gratis.
JunoCampos requeridos y tipos de entradarequired es un atributo booleano: está ahí o no, no se necesita valor. El type tanto elige el control como establece una regla de formato, así que type="email" quiere una @ y un dominio. Siempre comienza con el tipo más específico; es el marcado más pequeño para la mayor verificación, y los teclados móviles también mejoran.
JunoCampos requeridos y tipos de entrada Estas son las dos primeras capas de validación de restricción, el verificador de reglas integrado del navegador. Vale la pena recordar: type="email" es una verificación de forma flexible, no prueba de que la dirección sea real, y un tipo no compatible vuelve silenciosamente a text en lugar de romperse. Elegir el tipo es también una consideración de accesibilidad, ya que establece el teclado en pantalla que la gente obtiene.

Atributos de restricción

Más allá de "completado" y "tipo correcto", a menudo quieres límites: un valor de al menos esta longitud, un número no mayor que aquello, un código en un formato establecido. Un puñado de atributos cubrirán esto.

Puedes establecer límites en lo que acepta un campo. Dos útiles para comenzar:

html
<input type="text" name="username" maxlength="20">
<input type="number" name="quantity" min="1" max="10">

maxlength="20" detiene el campo una vez que contiene veinte caracteres. min y max establecen los números más pequeño y más grande permitidos. El navegador mantiene a las personas dentro de estos límites para que no tengas que verificarlos tú mismo.

Hay un pequeño conjunto de atributos de restricción, y cada uno se asigna a una regla:

  • minlength / maxlength establecen los caracteres menos y más que un valor de texto puede tener.
  • min / max establecen el valor más bajo y más alto para números y fechas.
  • step establece el incremento permitido para un número, así que step="5" acepta 0, 5, 10 y rechaza 3.
  • pattern contiene una expresión regular (una notación compacta para describir la forma exacta del texto permitido) que el valor debe coincidir completamente.
html
<input type="text" name="username" minlength="3" maxlength="20" required>
<input type="number" name="quantity" min="1" max="10" step="1">
<input type="text" name="pin" pattern="[0-9]{4}" title="Cuatro dígitos">

Empareja pattern con un title: algunos navegadores muestran su texto en el mensaje de error, y le dice a los usuarios que ven qué espera el campo. En CSS, :valid e :invalid te permiten dar estilo a un control basándote en si actualmente cumple sus restricciones.

Cada atributo de restricción corresponde a una bandera que el navegador puede establecer sobre un control, lo que importa una vez que lees el estado de validación desde JavaScript. min violado genera "desbordamiento de rango", maxlength excedido genera "demasiado largo", pattern que no coincide genera "patrón no coincide", y así sucesivamente. El atributo es la declaración; la bandera es cómo se reporta el resultado.

Dos detalles que confunden a la gente. pattern está implícitamente anclado: la expresión debe coincidir con todo el valor, como si estuviera envuelto para cubrir inicio a fin, así que pattern="[0-9]{4}" significa exactamente cuatro dígitos, no "contiene cuatro dígitos". Y step se mide desde una base (el min, o cero si no hay ninguno) usando aritmética de punto flotante, por lo que step="0.1" puede rechazar un valor que esperas que pase porque así es cómo el binario representa decimales; cuando la precisión importa, establece un min explícito como base.

Las pseudoclases :valid e :invalid (selectores CSS que coinciden con un elemento basándose en su estado en lugar de su posición) son útiles pero toscos: un campo required vacío coincide con :invalid desde el primer renderizado, por lo que colorearlo de rojo recibe al usuario con errores antes de que haya escrito nada. La solución es :user-invalid, que solo coincide después de que la persona ha interactuado con el campo, por lo que la retroalimentación llega cuando es útil en lugar de en la llegada.

La regla que evita la mayor confusión: un pattern debe coincidir con todo el valor, no solo una parte.

html
<input
  type="text"
  name="product_code"
  pattern="[A-Z]{2}-[0-9]{3}"
  title="Dos letras, un guión, tres dígitos, p. ej. AB-123"
  required
>
JunoAtributos de restricción Una vez que un campo es requerido y está tipado, puedes agregar límites. maxlength limita cuántos caracteres entran, y min y max limitan un número. El navegador mantiene a la gente dentro de esos límites automáticamente, lo que es mucha verificación que nunca tienes que escribir.
JunoAtributos de restricción El conjunto es pequeño: minlength/maxlength para texto, min/max para números y fechas, step para incrementos, y pattern para una forma exacta. Dale a pattern un title para que el mensaje signifique algo. Y pattern coincide con todo el valor, así que [0-9]{4} es cuatro dígitos en total, no cuatro dígitos en algún lugar dentro.
JunoAtributos de restricción Cada atributo se asigna a una bandera de validez que puedes leer después, por lo que el marcado y el reportaje se alinean. Dos trampa: pattern está anclado de principio a fin, y step usa matemáticas de punto flotante desde una base, así que establece un min explícito cuando la precisión cuenta. Estiliza con :user-invalid, no :invalid, o colorearás un campo sin tocar de rojo antes de que alguien escriba.

Retroalimentación nativa de validación

Declarar las reglas es la mitad de la historia. La otra mitad es lo que la persona ve cuando un valor incumple una.

Cuando alguien presiona el botón enviar y un campo es incorrecto, el navegador detiene el envío y muestra un pequeño mensaje junto al primer campo con un problema, luego mueve el cursor a él. No construyes este mensaje. El navegador lo escribe, en el idioma del visitante, y lo muestra automáticamente.

Así que un campo de correo electrónico required dejado en blanco produce algo como "Por favor completa este campo", y el formulario no se envía hasta que se corrija.

La validación se dispara al enviar. El navegador recorre los controles en orden, encuentra el primero inválido, lo enfoca y muestra un pequeño globo de mensaje describiendo el problema. Si todo pasa, el formulario se envía normalmente.

Obtienes algo de control de estilo a través de CSS. :required, :valid, :invalid e :in-range te permiten marcar campos visualmente. Lo que no puedes rediseñar fácilmente es el globo de mensaje en sí, su apariencia es del navegador, no la tuya. También puedes desactivar todo el sistema para un formulario con el atributo novalidate, lo que es útil cuando tienes la intención de validar completamente en JavaScript.

css
input:user-invalid {
  border-color: #c0392b;
}
input:user-valid {
  border-color: #2d7a3f;
}

La retroalimentación nativa es conveniente y casi gratuita, y viene con límites que vale la pena planificar antes de confiar en ella en un producto enviado.

El globo de mensaje es un elemento transitorio: aparece al enviar, desaparece en la siguiente interacción, y no es parte del documento que puedas seleccionar o estilizar. El soporte del lector de pantalla para él es desigual en los navegadores, por lo que un globo solo no es una forma confiable de anunciar un error a alguien que no pueda verlo. Su texto también está localizado al idioma del navegador, no al lang de tu página, por lo que un formulario escrito en inglés puede mostrar un mensaje de error en francés a un visitante cuyo navegador está configurado en francés. Eso es correcto para el usuario y sorprendente para el desarrollador.

Puedes activar el mismo flujo tú mismo sin enviar: reportValidity() ejecuta las verificaciones y muestra los globos bajo demanda. Pero para cualquier cosa más allá de un prototipo rápido, el patrón accesible es suprimir el globo nativo (con novalidate) y renderizar tu propio texto de error en la página, vinculado al campo, para que sea visible, estilizable y anunciado de manera confiable. La siguiente sección cubre cómo.

JunoRetroalimentación nativa de validación Aquí viene el pago por todos esos atributos: presiona enviar con un campo malo y el navegador muestra un pequeño mensaje, te señala el campo y se rehúsa a enviar. No escribiste ninguno de ese texto; el navegador lo hizo, en el idioma del visitante. Para muchos formularios, esta es toda la retroalimentación que necesitas.
JunoRetroalimentación nativa de validación La verificación se ejecuta al enviar: el primer campo malo obtiene el foco y un globo de mensaje, y el formulario se detiene hasta que pase. Puedes estilizar los campos con :user-valid e :user-invalid, pero el globo en sí es del navegador para diseñar. Desactívalo todo con novalidate cuando planees manejarlo en JavaScript.
JunoRetroalimentación nativa de validación El globo es transitorio e iestilizable, su soporte de lector de pantalla es deficiente, y su texto sigue el idioma del navegador, no el de tu página. Está bien para un prototipo, es insuficiente para producción. Cuando la accesibilidad importa, agrega novalidate y renderiza tu propio texto de error en la página para que pueda verse, estilizarse y anunciarse apropiadamente.

Cuándo aún necesitas JavaScript

La validación nativa verifica un campo contra sus propias reglas. Muchas verificaciones reales no caben en esa forma, y eso es donde JavaScript entra.

Algunas cosas que el navegador no puede verificar por sí solo. Si dos campos de contraseña coinciden entre sí. Si un nombre de usuario ya está siendo usado por otra persona. Si un código de descuento es real. Ninguno de estos se trata de la forma de un campo, por lo que no hay atributo para ellos, y buscas JavaScript para hacer la verificación.

Eso no es un fracaso de HTML. Las verificaciones integradas manejan los casos comunes sin código, y JavaScript maneja el resto.

La brecha es cualquier cosa que dependa de más de un campo, o de información que la página aún no tiene. Un ejemplo común es confirmar que dos contraseñas coincidan. Los comparas en JavaScript e introduces el resultado nuevamente en el sistema nativo con setCustomValidity, que establece un mensaje de error personalizado en un control (una cadena vacía significa "esto es válido"):

html
<input type="password" id="password" name="password" required>
<input type="password" id="confirm" name="confirm" required>

La comparación en sí se ejecuta en un <script>:

js
const password = document.querySelector("#password")
const confirm = document.querySelector("#confirm")

confirm.addEventListener("input", () => {
  // cadena vacía limpia el error y marca el campo como válido
  const message = confirm.value === password.value ? "" : "Las contraseñas no coinciden"
  confirm.setCustomValidity(message)
})

El campo ahora participa en la validación nativa como cualquier otro, bloqueando el envío y mostrando tu mensaje cuando las contraseñas difieren. Las verificaciones que necesitan un servidor, como la disponibilidad del nombre de usuario, siguen la misma forma pero establecen el mensaje después de que vuelve una solicitud de red.

JavaScript accede a la validación nativa a través de la API de Validación de Restricción (el conjunto de propiedades y métodos que el navegador expone en controles de formulario para leer y establecer validez). Los elementos que usas más:

  • setCustomValidity(message) establece una cadena de error personalizada en un control. Una cadena no vacía lo marca como inválido y se convierte en su mensaje; una cadena vacía limpia el error personalizado.
  • validity es un objeto ValidityState de solo lectura: un booleano por cada posible fallo (valueMissing, typeMismatch, patternMismatch, rangeOverflow, tooLong, customError, y así sucesivamente), más valid para el resultado general. Léelo para descubrir por qué un campo falló, no solo que falló.
  • checkValidity() devuelve verdadero o falso sin mostrar nada; reportValidity() hace lo mismo y muestra los globos nativos.

Para errores accesibles, el color nunca es suficiente por sí solo: un borde rojo no dice nada a un lector de pantalla o a alguien que no pueda distinguir el rojo del verde. Asocia el texto de error con su campo para que la tecnología de asistencia los lea juntos. Establece aria-describedby en la entrada al id del elemento de error (esto le dice a un lector de pantalla "lee este texto como la descripción del campo"), y establece aria-invalid="true" mientras el campo está fallando (esto anuncia el campo como estando en un estado de error):

html
<input
  type="text"
  id="username"
  name="username"
  aria-describedby="username-error"
  aria-invalid="true"
  required
>
<p id="username-error" role="alert">Ese nombre de usuario ya está en uso.</p>

El role="alert" hace que un lector de pantalla anuncie el mensaje tan pronto como aparece. El capítulo Accesibilidad profundiza en asociar controles con sus descripciones.

Eso deja el equilibrio. La validación nativa es menos código, es consistente con la plataforma, y funciona antes de que tu script cargue, pero su retroalimentación es difícil de estilizar y anunciar. La validación personalizada es más código y más responsabilidad, pero te da control total sobre la redacción, el tiempo y los atributos de accesibilidad. La mayoría de los formularios de producción usan ambos: atributos nativos como línea de base, JavaScript en capas para las verificaciones y el mensajería que los atributos no pueden alcanzar.

Cualquiera que elijas, una regla no se dobla. La validación del cliente es una conveniencia, nunca una garantía. Todo lo descrito en este capítulo se ejecuta en el navegador del visitante, donde cualquiera puede desactivarlo, editar la página o enviar una solicitud directamente a tu servidor sin tocar el formulario en absoluto. El servidor debe revalidar cada valor que recibe como si nunca hubiera sucedido ninguna verificación del navegador. Trata las verificaciones del navegador como un primer pase rápido y amigable que le evita a tus usuarios un viaje de ida y vuelta, y trata el servidor como la verificación que realmente protege tus datos.

JunoCuándo aún necesitas JavaScript Las verificaciones integradas cubren un campo a la vez, así que cualquier cosa que compare campos, como "¿coinciden estas dos contraseñas", o verificar con un servidor, como "¿está disponible este nombre de usuario", necesita JavaScript. Eso es normal. HTML maneja los casos cotidianos gratis, y JavaScript recoge los que no puede ver por sí solo.
JunoCuándo aún necesitas JavaScript Cuando una verificación abarca dos campos o necesita el servidor, cómputala en JavaScript e introduce el resultado nuevamente con setCustomValidity: una cadena de mensaje para fallar el campo, una cadena vacía para limpiarlo. El campo luego se une a la validación nativa como cualquier otro. La coincidencia de contraseña y la disponibilidad del nombre de usuario son los dos clásicos.
JunoCuándo aún necesitas JavaScript La API de Validación de Restricción es tu gancho: setCustomValidity para establecer un mensaje, validity para leer por qué un campo falló, checkValidity y reportValidity para ejecutar las verificaciones. Para errores accesibles, vincula el texto al campo con aria-describedby e aria-invalid, nunca solo color. Y el que no es opcional: cada verificación aquí se ejecuta en el navegador, por lo que el servidor tiene que revalidar todo.