Saltar al contenido
formularios-avanzados.html · devschool

Más campos de formulario en HTML

Lección 18 de 39 · 12 min de lectura · Actualizado el

En esta lección
  1. Todos los tipos de <input> de un vistazo
  2. <select> a fondo
  3. Sugerencias con <datalist>
  4. <textarea> en detalle
  5. Agrupar y desactivar: <fieldset disabled>
  6. Mostrar resultados con <output>
  7. Varios botones en un formulario
  8. Campos fuera del <form>: el atributo form
  9. Subir archivos
  10. Ayudas para escribir en el móvil
  11. Qué envía realmente un formulario
  12. Errores frecuentes
  13. Resumen

Ya sabes montar un formulario con <form>, <label>, <input> y botones, y en validar formularios verás cómo hacer que el navegador compruebe los datos. Esta lección recoge todo lo demás: los detalles de <select> y <textarea>, las sugerencias con <datalist>, el elemento <output>, formularios con varios botones que hacen cosas distintas, campos que viven fuera del <form>, la subida de archivos y unos cuantos atributos que mejoran mucho la experiencia en el móvil. Al final sabrás exactamente qué datos envía un formulario y en qué forma.

Todos los tipos de <input> de un vistazo

El atributo type cambia por completo el comportamiento de un <input>. Esta es la lista completa, para que la tengas a mano:

GrupoTiposDónde se explican
Textotext, search, email, url, tel, passwordValidar formularios
Númerosnumber, rangeValidar formularios
Fechas y horasdate, time, datetime-local, month, weekValidar formularios
Elegircheckbox, radio, colorFormularios y aquí
ArchivosfileAquí, más abajo
OcultoshiddenValidar formularios
Botonessubmit, reset, button, imageAquí, más abajo

Si escribes un type que no existe (o que un navegador muy antiguo no conoce), el campo se comporta como text. Por eso los tipos modernos se pueden usar sin miedo: en el peor caso, el usuario ve una caja de texto normal.

<select> a fondo

Grupos de opciones: <optgroup>

En una lista larga, agrupa las opciones con <optgroup>. Su label aparece como un título que no se puede elegir:

<label for="ruta">Ruta</label>
<select id="ruta" name="ruta">
  <optgroup label="Fácil">
    <option value="senda-costera">Senda costera (8 km)</option>
    <option value="bosque-hayas">Bosque de hayas (6 km)</option>
  </optgroup>
  <optgroup label="Difícil">
    <option value="pico-cueto">Pico del Cueto (14 km)</option>
  </optgroup>
</select>

Los grupos no se pueden anidar: un <optgroup> no puede contener otro.

value, selected y disabled

  • Lo que se envía es el value de la opción. El texto entre las etiquetas es lo que ve el usuario. Si no pones value, se envía el texto.
  • selected marca la opción elegida al cargar la página. Sin él, se elige la primera.
  • disabled en una <option> la muestra en gris y no se puede elegir (útil para “completo” o “agotado”). En un <optgroup> desactiva todo el grupo.

Una opción vacía para obligar a elegir

Si el <select> tiene required y la primera opción tiene value="", el navegador no deja enviar hasta que el usuario elija otra:

<select name="talla" required>
  <option value="">Elige tu talla</option>
  <option value="s">S</option>
  <option value="m">M</option>
  <option value="l">L</option>
</select>

Así evitas que se envíe la primera talla de la lista solo porque nadie la cambió.

Elegir varias: multiple y size

Con multiple, el usuario puede marcar varias opciones (con Ctrl o Cmd + clic). size indica cuántas filas se ven a la vez:

<label for="dias">Días que puedes venir</label>
<select id="dias" name="dias" multiple size="4">
  <option value="lun">Lunes</option>
  <option value="mar">Martes</option>
  <option value="mie">Miércoles</option>
  <option value="jue">Jueves</option>
</select>

Mucha gente no sabe usar un <select multiple> en el ordenador. Si son pocas opciones, un grupo de casillas (checkbox) suele ser más claro.

Sugerencias con <datalist>

En formularios viste que <datalist> propone valores en un campo de texto, pero deja escribir otros. Se conecta con el atributo list, que debe coincidir con el id del <datalist>. Además de texto, funciona con otros tipos:

<!-- Marcas en un deslizador -->
<label for="volumen">Volumen</label>
<input id="volumen" name="volumen" type="range" min="0" max="100" list="marcas">
<datalist id="marcas">
  <option value="0"></option>
  <option value="50"></option>
  <option value="100"></option>
</datalist>

<!-- Colores sugeridos -->
<label for="color">Color del dorsal</label>
<input id="color" name="color" type="color" list="colores">
<datalist id="colores">
  <option value="#e03131"></option>
  <option value="#1971c2"></option>
  <option value="#2f9e44"></option>
</datalist>

Cuándo elegir cada uno:

Necesito…Uso
Que solo se pueda elegir de una lista cerrada<select>
Proponer valores, pero aceptar cualquier otro<input list> + <datalist>
Pocas opciones que se vean todas a la vezradio o checkbox

Cuidado: el aspecto de las sugerencias de <datalist> lo decide cada navegador y no se puede cambiar con CSS. Tampoco es una validación: el usuario puede escribir lo que quiera.

<textarea> en detalle

<label for="comentario">Cuéntanos tu experiencia</label>
<textarea id="comentario" name="comentario" rows="5" maxlength="500"
          placeholder="Qué te gustó, qué mejorarías..."></textarea>
  • rows es el número de líneas visibles; cols, la anchura en caracteres. Hoy la anchura se suele dar con CSS (width: 100%).
  • maxlength y minlength funcionan igual que en <input>.
  • No tiene atributo value. El texto inicial va entre las etiquetas, y se respeta tal cual, con sus espacios y saltos de línea. Por eso <textarea> y </textarea> se escriben pegados cuando debe empezar vacío: cualquier espacio entre ellos sería contenido.
  • Con CSS, resize: vertical deja que el usuario lo agrande solo en altura, y field-sizing: content (de momento, solo en algunos navegadores) hace que crezca solo según lo que se escribe.

Agrupar y desactivar: <fieldset disabled>

<fieldset> y <legend> ya los conoces de formularios. Un detalle útil: disabled en un <fieldset> desactiva todos los campos de dentro de golpe, y ninguno se envía:

<label><input type="checkbox" id="factura"> Quiero factura</label>

<fieldset id="datos-factura" disabled>
  <legend>Datos de facturación</legend>
  <label for="nif">NIF</label>
  <input id="nif" name="nif">
  <label for="razon">Razón social</label>
  <input id="razon" name="razon">
</fieldset>

Con una línea de JavaScript activas el grupo cuando se marca la casilla:

const casilla = document.querySelector('#factura');
const grupo = document.querySelector('#datos-factura');
casilla.addEventListener('change', () => {
  grupo.disabled = !casilla.checked;
});

Mostrar resultados con <output>

<output> contiene el resultado de un cálculo hecho con otros campos: un total, una media, un precio final. El atributo for lista los id de los campos de los que depende, separados por espacios:

<form id="calculo">
  <label for="sesiones">Sesiones</label>
  <input id="sesiones" type="number" min="1" value="4">
  <label for="precio">Precio por sesión</label>
  <input id="precio" type="number" min="0" value="12">
  <p>Total: <output id="total" for="sesiones precio">48</output> €</p>
</form>
const formulario = document.querySelector('#calculo');
formulario.addEventListener('input', () => {
  const sesiones = Number(formulario.querySelector('#sesiones').value);
  const precio = Number(formulario.querySelector('#precio').value);
  formulario.querySelector('#total').value = sesiones * precio;
});

El evento input del formulario se dispara cada vez que cambia cualquiera de sus campos, así que no hace falta escuchar uno por uno. Los lectores de pantalla anuncian los cambios de un <output> sin que tengas que hacer nada más.

Nota: un <output> no se envía con el formulario, aunque tenga name. Si el servidor necesita el total, debe calcularlo él (y debería hacerlo siempre: nunca te fíes de un precio que llega del navegador).

Sus parientes <progress> (avance de una tarea) y <meter> (una medida dentro de un rango) se explican en details y dialog.

Varios botones en un formulario

Saber qué botón se pulsó: name y value

Un botón de envío puede llevar name y value. Solo se envía el del botón que se ha pulsado:

<form action="/comentario" method="post">
  <label for="texto">Comentario</label>
  <textarea id="texto" name="texto"></textarea>
  <button type="submit" name="accion" value="publicar">Publicar</button>
  <button type="submit" name="accion" value="borrador">Guardar borrador</button>
</form>

El servidor recibe accion=publicar o accion=borrador y actúa en consecuencia.

Cambiar el destino: formaction y compañía

Los botones de envío pueden sobrescribir los atributos del formulario solo cuando se pulsan ellos:

Atributo del botónSustituye a… del <form>
formactionaction: a qué dirección se envía
formmethodmethod: get o post
formenctypeenctype: cómo se codifican los datos
formtargettarget: dónde se abre la respuesta
formnovalidatenovalidate: enviar sin validar
<form action="/pedido" method="post">
  ...
  <button type="submit">Confirmar pedido</button>
  <button type="submit" formaction="/presupuesto" formtarget="_blank" formnovalidate>
    Ver presupuesto en PDF
  </button>
</form>

El ejemplo del editor usa esta idea: prueba los dos botones y mira a qué dirección iría cada envío.

<input type="image"> y reset

  • type="image" es un botón de envío con forma de imagen (src y alt obligatorios). Hoy es más flexible un <button> con una imagen o un icono dentro.
  • type="reset" vacía el formulario. Casi nunca es buena idea: es fácil pulsarlo sin querer y perder todo.

Campos fuera del <form>: el atributo form

A veces el diseño obliga a colocar un campo o un botón fuera del <form>, por ejemplo en una barra fija abajo. El atributo form lo asocia por id:

<form id="buscador" action="/buscar" method="get">
  <label for="q">Buscar</label>
  <input id="q" name="q" type="search">
</form>

<!-- En otra parte de la página -->
<button type="submit" form="buscador">Buscar</button>

El botón envía el formulario buscador aunque no esté dentro de él. Funciona en <input>, <select>, <textarea>, <button>, <output> y <fieldset>.

Para marcar una zona de búsqueda o de filtros, HTML tiene el elemento <search>. Envuelve al formulario y le dice a los lectores de pantalla que ahí se busca:

<search>
  <form action="/buscar" method="get">
    <label for="busqueda">Buscar curso</label>
    <input id="busqueda" name="q" type="search">
    <button>Buscar</button>
  </form>
</search>

Subir archivos

Lo básico (type="file", accept, multiple y enctype="multipart/form-data") lo tienes en validar formularios. Algunos detalles más:

<form action="/subir" method="post" enctype="multipart/form-data">
  <label for="fotos">Fotos de la ruta (máximo 5)</label>
  <input id="fotos" name="fotos" type="file" accept="image/*" multiple>

  <label for="selfi">Foto para el carné</label>
  <input id="selfi" name="selfi" type="file" accept="image/*" capture="user">

  <button>Subir</button>
</form>
  • accept="image/*" acepta cualquier imagen; también puedes combinar tipos y extensiones: accept=".pdf,.docx".
  • capture pide al móvil que abra directamente la cámara: user es la frontal y environment, la trasera. En el ordenador se ignora.
  • HTML no puede limitar el número de archivos ni su tamaño. Eso se comprueba con JavaScript (leyendo input.files) y, sobre todo, en el servidor. Lo verás en ficheros y subidas con PHP.
  • Por seguridad, un <input type="file"> no admite un value inicial: el usuario siempre tiene que elegir el archivo.

Ayudas para escribir en el móvil

Además de autocomplete, inputmode y enterkeyhint (explicados en validar formularios), hay tres atributos más que evitan pequeñas molestias:

AtributoValoresPara qué
autocapitalizeoff, sentences, words, charactersEvita que el móvil ponga la primera letra en mayúscula en un usuario o un código
spellchecktrue, falseQuita el subrayado rojo en campos donde no tiene sentido (códigos, nombres de usuario)
autofocus(booleano)Pone el cursor en ese campo al cargar. Úsalo solo en páginas cuyo único fin es ese formulario, como un buscador
<label for="usuario">Usuario</label>
<input id="usuario" name="usuario" autocomplete="username"
       autocapitalize="off" spellcheck="false">

En formularios de compra, autocomplete admite además prefijos para separar direcciones: shipping street-address (dirección de envío) y billing street-address (dirección de facturación). Así el navegador no mezcla las dos.

Qué envía realmente un formulario

Entender esto te ahorrará muchos errores cuando programes el servidor. Al enviar:

  • Solo se envían los campos con name.
  • Los campos disabled (o dentro de un <fieldset disabled>) no se envían. Los readonly, sí.
  • Las casillas y los radios sin marcar no envían nada: ni siquiera su nombre.
  • Varias casillas con el mismo name, o un <select multiple>, envían el nombre repetido, una vez por cada valor.
  • Los <output> no se envían.
  • Los espacios y caracteres especiales se codifican: un espacio se convierte en + y la ó en %C3%B3.

Por ejemplo, con dos casillas intereses marcadas (bloque y yoga) y el campo cupon asociado con form, un envío con method="get" produce esta consulta en la URL:

?intereses=bloque&intereses=yoga&cupon=VERANO

Consejo: en PHP, para recibir varios valores con el mismo nombre como un array, el name debe terminar en corchetes: name="intereses[]". Otros lenguajes de servidor leen todos los valores sin necesidad de corchetes.

Errores frecuentes

  • <select required> cuya primera opción tiene valor. La validación nunca falla, porque ya hay algo elegido. La opción de “Elige una” debe tener value="".
  • Escribir el texto inicial de un <textarea> en value. No existe; va entre las etiquetas.
  • Dejar espacios entre <textarea> y </textarea>. El campo aparece con espacios en blanco dentro.
  • Esperar que un <output> llegue al servidor. No se envía; calcula el resultado en el servidor.
  • Botones sin type que no deberían enviar. Un <button> dentro de un formulario envía por defecto. Si es para otra cosa, type="button".
  • Confiar en accept para filtrar archivos peligrosos. Es solo una ayuda para el usuario; el servidor debe revisar tipo y tamaño.
  • Desactivar con disabled un dato que el servidor necesita. No se enviará. Si solo quieres que no se edite, usa readonly.

Resumen

Quiero…Uso
Agrupar opciones de un desplegable<optgroup label="...">
Obligar a elegir en un <select>required + primera opción con value=""
Elegir varias opciones<select multiple size="..."> o casillas
Sugerir valores sin cerrar la lista<input list> + <datalist>
Texto largo con valor inicial<textarea>texto</textarea>
Desactivar un grupo de campos<fieldset disabled>
Mostrar un resultado calculado<output for="...">
Saber qué botón se pulsóname y value en el botón
Que un botón envíe a otra direcciónformaction, formmethod, formtarget
Un campo fuera del formularioAtributo form="id-del-form"
Abrir la cámara del móviltype="file" + accept="image/*" + capture
Evitar mayúsculas automáticasautocapitalize="off"

En la siguiente lección aprenderás a comprobar los datos antes de enviarlos: validar formularios.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

formularios-avanzados.html
Resultado

Pon a prueba lo que has aprendido

[HTML] Tienes <select required> y la primera opción es <option>Elige una</option>. ¿Qué falla?

[HTML] ¿Cómo se pone el texto inicial de un <textarea>?

[HTML] El campo <input name="dni" disabled> está dentro de un formulario. Al enviarlo...

[HTML] ¿Qué hace formaction en un botón de envío?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.