Validar formularios en HTML
En esta lección
En la lección de formularios viste la estructura básica: <form>, <label>, name, los botones y los tipos de campo más comunes. Ahora vamos a por todos los tipos de <input> de HTML5 y a por la validación nativa: cómo conseguir que el navegador compruebe los datos antes de enviarlos, sin escribir una línea de JavaScript. Elegir bien el tipo de campo mejora mucho la experiencia en el móvil, porque cambia el teclado que aparece.
Tipos de texto
type | Para qué | Qué aporta |
|---|---|---|
text | Texto corto genérico | Es el valor por defecto |
email | Direcciones de correo | Valida el formato y muestra un teclado con @ |
password | Contraseñas | Oculta lo que se escribe |
search | Cajas de búsqueda | Muestra una X para borrar y la tecla “Buscar” |
tel | Teléfonos | Teclado numérico de teléfono. No valida el formato |
url | Direcciones web | Exige algo como https://... |
<label for="web">Tu web o portfolio</label>
<input id="web" name="web" type="url" placeholder="https://...">
<label for="movil">Móvil</label>
<input id="movil" name="movil" type="tel" autocomplete="tel">
email admite multiple para aceptar varias direcciones separadas por comas.
Números y rangos
number
<label for="personas">Número de personas</label>
<input id="personas" name="personas" type="number" min="1" max="8" step="1" value="1">
minymax: valores mínimo y máximo.step: de cuánto en cuánto. Constep="0.5"se admiten 1, 1.5, 2… Constep="any"se admite cualquier decimal.
Cuidado: usa
numbersolo para cantidades. Un código postal, un DNI o un número de tarjeta no son cantidades: no se suman, pueden empezar por cero y las flechas de subir y bajar no tienen sentido. Para eso,type="text"coninputmode="numeric"(lo verás más abajo).
range
Un deslizador. No muestra el valor exacto, así que es para cuando la precisión no importa: volumen, nivel de dificultad, “¿cuánto te ha gustado?”.
<input type="range" name="dificultad" min="1" max="10" step="1" value="5">
Fechas y horas
type | Formato del valor enviado | Ejemplo |
|---|---|---|
date | AAAA-MM-DD | 2026-10-15 |
time | HH:MM | 18:30 |
datetime-local | AAAA-MM-DDTHH:MM | 2026-10-15T18:30 |
month | AAAA-MM | 2026-10 |
week | AAAA-Www | 2026-W42 |
El navegador muestra un calendario o un selector de hora en el formato local del usuario (en España, 15/10/2026), pero envía siempre el formato de la tabla. Así el servidor no tiene que adivinar si 03/04 es 3 de abril o 4 de marzo.
<label for="dia">Día de la clase de prueba</label>
<input id="dia" name="dia" type="date" min="2026-10-01" max="2026-12-20" required>
<label for="hora">Hora</label>
<input id="hora" name="hora" type="time" min="10:00" max="21:00" step="1800">
En time, step va en segundos: 1800 son intervalos de media hora.
month y week no tienen selector en todos los navegadores (Firefox y Safari muestran un campo de texto). Úsalos con cuidado.
Otros tipos
color
Abre un selector de color y envía el valor en hexadecimal, por ejemplo #ff8800:
<label for="color-camiseta">Color de la camiseta</label>
<input id="color-camiseta" name="color" type="color" value="#1971c2">
file
Permite subir archivos. Para que funcione, el formulario debe usar method="post" y enctype="multipart/form-data":
<form action="/inscripcion" method="post" enctype="multipart/form-data">
<label for="foto">Foto para el carné (JPG o PNG)</label>
<input id="foto" name="foto" type="file" accept="image/png, image/jpeg">
<label for="docs">Documentos</label>
<input id="docs" name="docs" type="file" accept=".pdf" multiple>
</form>
acceptfiltra los archivos que se proponen: por tipo (image/*,image/png) o por extensión (.pdf). Es una ayuda, no una protección: el servidor debe comprobarlo igualmente.multiplepermite elegir varios archivos a la vez.
checkbox y radio
Ya los conoces. Recuerda que los radio del mismo grupo comparten name, y que ambos envían su value (si un checkbox no tiene value, envía on). Si un checkbox no está marcado, no se envía nada.
hidden
Un campo invisible que se envía con el formulario. Sirve para datos que el usuario no tiene que ver ni tocar:
<input type="hidden" name="origen" value="pagina-tarifas">
Cuidado: “oculto” no significa “secreto”. Cualquiera puede verlo y cambiarlo con las herramientas del navegador.
Validación nativa
Cuando pulsas el botón de enviar, el navegador comprueba cada campo. Si algo no cumple, no envía el formulario, pone el foco en el primer campo con error y muestra un mensaje.
Los atributos de validación
| Atributo | Se aplica a | Comprueba |
|---|---|---|
required | Casi todos | Que no esté vacío (en checkbox, que esté marcado) |
minlength / maxlength | Texto | Número de caracteres |
min / max | Números, fechas, horas, range | Valor mínimo y máximo |
step | Números, fechas, horas | Que el valor encaje en los saltos |
pattern | Texto, tel, email, url, search, password | Que cumpla una expresión regular |
type | email, url | Que tenga el formato correcto |
maxlength es especial: no muestra error, directamente impide escribir más caracteres.
pattern con ejemplos
pattern usa una expresión regular (lo verás a fondo en expresiones regulares). Debe coincidir con todo el valor, no con una parte:
<!-- Código postal español: 5 cifras -->
<input name="cp" pattern="[0-9]{5}" title="5 cifras, por ejemplo 39005">
<!-- Móvil español: empieza por 6 o 7 y tiene 9 cifras -->
<input name="movil" type="tel" pattern="[67][0-9]{8}">
<!-- Usuario: letras minúsculas, números y guion bajo, de 3 a 15 caracteres -->
<input name="usuario" pattern="[a-z0-9_]{3,15}">
El title se añade al mensaje de error en muchos navegadores. Explica ahí el formato esperado.
Un campo vacío sin required se considera válido aunque tenga pattern: el patrón solo se comprueba si hay algo escrito.
Desactivar la validación: novalidate y formnovalidate
<form action="/perfil" method="post" novalidate>
...
</form>
novalidate en el <form> desactiva todas las comprobaciones. Se usa cuando validas con JavaScript y quieres mostrar tus propios mensajes.
formnovalidate va en un botón concreto. Es útil para un “Guardar borrador” que debe funcionar aunque falten campos obligatorios:
<button type="submit">Publicar</button>
<button type="submit" formnovalidate name="borrador" value="1">Guardar borrador</button>
Dar estilo según el estado: :valid, :invalid y :user-invalid
CSS tiene pseudoclases que se activan según la validez del campo:
input:invalid { border-color: #c92a2a; }
input:valid { border-color: #2f9e44; }
input:user-invalid { background: #fff5f5; }
El problema de :invalid es que se activa nada más cargar la página: un campo required vacío ya está en rojo antes de que el usuario haga nada. :user-invalid solo se activa después de que el usuario haya interactuado con el campo (o intentado enviar), que es lo que suele quererse.
Recuerda: la validación del navegador es comodidad para el usuario, no seguridad. Cualquiera puede quitar un
requiredcon F12. El servidor debe validar siempre.
Ayudas para rellenar: autocomplete, inputmode y enterkeyhint
autocomplete
Le dice al navegador qué dato va en el campo, para que lo rellene con lo que tiene guardado. Ahorra mucho tiempo, sobre todo en el móvil:
| Valor | Dato |
|---|---|
name, given-name, family-name | Nombre completo, nombre, apellidos |
email, tel | Correo y teléfono |
street-address, postal-code, address-level2, country-name | Dirección, código postal, ciudad, país |
username | Nombre de usuario |
current-password | Contraseña al iniciar sesión |
new-password | Contraseña nueva (el navegador puede sugerir una segura) |
one-time-code | Código de verificación de un SMS |
bday | Fecha de nacimiento |
off | No autocompletar |
<input name="usuario" autocomplete="username">
<input name="clave" type="password" autocomplete="current-password">
inputmode
Elige el teclado del móvil sin cambiar el tipo de campo ni añadir validación:
| Valor | Teclado |
|---|---|
numeric | Solo cifras |
decimal | Cifras y separador decimal |
tel | Teclado de teléfono |
email | Con @ y . |
url | Con / y .com |
search | Con tecla de buscar |
none | Ninguno (para teclados propios) |
<input name="cp" inputmode="numeric" pattern="[0-9]{5}" autocomplete="postal-code">
Esta es la forma correcta de pedir un código postal: texto, teclado numérico y patrón.
enterkeyhint
Cambia el texto de la tecla Intro del teclado del móvil: enter, done, go, next, previous, search o send.
<input name="mensaje" enterkeyhint="send">
Errores frecuentes
type="number"para códigos o DNI. Pierde los ceros iniciales y muestra flechas absurdas. Usa texto coninputmode="numeric".- Olvidar
enctype="multipart/form-data"al subir archivos: solo se envía el nombre del archivo. patterncon^y$pensando que hacen falta. El navegador ya exige que coincida el valor entero.- Confiar en
accepto enrequiredcomo seguridad. Se saltan fácilmente. - Estilos con
:invalidque ponen todo en rojo antes de empezar. Usa:user-invalid. - Poner
autocomplete="off"en todo. Fastidia a los usuarios y los gestores de contraseñas lo ignoran.
Resumen
| Quiero… | Uso |
|---|---|
| Un email, una web, un teléfono | type="email", type="url", type="tel" |
| Una cantidad | type="number" con min, max, step |
| Un deslizador | type="range" |
| Una fecha u hora | date, time, datetime-local, month, week |
| Subir archivos | type="file" + accept + multiple + enctype |
| Campo obligatorio | required |
| Formato concreto | pattern + title |
| Longitud de texto | minlength, maxlength |
| Rellenar automáticamente | autocomplete con el valor adecuado |
| Otro teclado en el móvil | inputmode y enterkeyhint |
| Validar con JavaScript | novalidate en el form |
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.