← HTML: estructura y contenido de la Web

Sesión 5

Formularios accesibles

Tres horas. Media hora para entender qué une una etiqueta con su campo y qué valida el navegador por sí solo, y dos horas y media construyendo un formulario completo, auditando uno defectuoso y recorriéndolo todo sin ratón.

Hoy · Hoja de ruta

  1. 1. Aprende: Qué une un label con su campo, en qué se diferencian id y name, qué te da gratis elegir bien el type y cómo se agrupan las opciones.
  2. 2. Haz: Construye el formulario de contacto en tres fases, audita uno defectuoso y revisa el de un compañero.
  3. 3. Comprueba: Recorre el formulario entero usando solo el teclado.

Antes de empezar · 5 minutos, sin apuntes

  1. ¿Qué pregunta permite decidir si unos datos necesitan una tabla?
  2. ¿Qué elemento usarías para una acción y cuál para ir a otra página?
  3. Marca semánticamente: título, navegación, noticia independiente y lista de características.

Se explica

25 minutos · conceptos y demostración

Un formulario es la parte de una web donde los errores de marcado dejan de ser teóricos: un campo mal etiquetado no es feo, es un campo que alguien no puede rellenar. Casi todo lo de hoy se comprueba soltando el ratón.

El formulario mínimo

<form>
    <label for="nombre">Nombre</label>

    <input id="nombre" name="nombre" type="text">

    <button type="submit">Enviar</button>
</form>
Vista previaEtiqueta, campo y acción

Esta vista es ilustrativa. Al ejecutar el código real, pulsar «Nombre» lleva el foco al campo porque el for del label coincide con su id.

Tres piezas: el contenedor form, los controles, y el botón que lo envía. Lo que casi siempre se hace mal es la relación entre cada campo y su etiqueta.

La etiqueta pertenece al campo

Etiqueta asociada

Un <label for="x"> unido a un <input id="x">. El texto no queda simplemente situado junto al campo, sino que pertenece a él.

Qué se gana asociándola:

  1. Un lector de pantalla anuncia el campo con su nombre. Sin label, anuncia «campo de texto» y nada más.
  2. El área de clic crece: pulsar sobre el texto lleva el cursor al campo. Importa mucho en pantallas pequeñas y en casillas de verificación.
  3. El navegador puede autocompletar mejor.

El placeholder no es una etiqueta

El texto gris dentro de un campo desaparece en cuanto empiezas a escribir. Quien interrumpe la cumplimentación de un formulario largo encuentra un campo relleno sin indicación de qué contenía. Quien emplea un lector de pantalla puede no oírlo nunca.

Sirve para dar un ejemplo de formato junto a una etiqueta —placeholder="nombre@example.com"—, nunca para sustituirla.

id y name no son lo mismo

<input id="nombre" name="nombre">

Se escriben casi siempre iguales, y por eso se confunden.

Atributo Para quién es Qué hace
id Para el documento Identifica el campo dentro de la página; es a lo que apunta el for
name Para el servidor Es el nombre con el que el dato viaja al enviarse

Un campo sin name se rellena perfectamente y su dato no llega a ninguna parte. Cuando estudies servidores verás por qué.

El botón que envía es un <button type="submit">. No conviertas un texto o una imagen en un falso botón: un <div> no se alcanza con Tab, no se activa con Enter y no se anuncia como botón.

El tipo no es cosmético

Todos estos campos aceptan texto. La diferencia está en lo que el navegador hace con ellos sin que tú programes nada.

<input type="email"    id="email"     name="email">
<input type="password" id="password"  name="password">
<input type="number"   id="unidades"  name="unidades" min="1" max="10">
<input type="date"     id="fecha"     name="fecha">
<input type="url"      id="web"       name="web">
<input type="tel"      id="telefono"  name="telefono">
type Qué aporta de fábrica
text Nada especial
email Comprueba el formato y ofrece un teclado con arroba en el móvil
password Oculta lo escrito
number Solo acepta números, admite min y max
date Un selector de fecha del sistema, ya traducido
url Comprueba que sea una dirección web
tel Teclado numérico de teléfono en el móvil

Ese teclado adaptado determina la diferencia entre completar un formulario con comodidad en el móvil y abandonarlo, de modo que no constituye un detalle menor.

Validación nativa

Validación nativa

Las comprobaciones que hace el propio navegador antes de enviar, declaradas con atributos y sin una línea de JavaScript.

<input type="email" required>

<input type="text" minlength="3" maxlength="50">

<input type="number" min="1" max="100">
Atributo Exige
required Que el campo no se quede vacío
minlength / maxlength Un número mínimo y máximo de caracteres
min / max Un valor mínimo y máximo, en números y fechas
pattern Que el texto encaje con un patrón concreto

La pregunta antes de escribir JavaScript

Antes de programar una validación, pregúntate: ¿HTML ya sabe hacerlo? Muchas veces sí, y la versión nativa funciona mejor, es más accesible y no hay que mantenerla.

Validar en el navegador no es validar

Todo esto se puede desactivar: basta con enviar la petición sin pasar por el formulario. La validación nativa está para ayudar a quien rellena, avisándole antes de enviar y sin recargar la página.

La comprobación que de verdad protege los datos se hace en el servidor, y la verás en otro módulo. Las dos son necesarias y no se sustituyen.

Agrupar opciones

La exclusión mutua entre botones de opción la produce compartir exactamente el mismo name, no el fieldset. Si puedes marcar los dos a la vez, tienen name distinto.

<fieldset>

    <legend>Modalidad de envío</legend>

    <label>
        <input type="radio" name="envio" value="estandar">
        Estándar
    </label>

    <label>
        <input type="radio" name="envio" value="urgente">
        Urgente
    </label>

</fieldset>

Fíjate también en otra forma de asociar la etiqueta: aquí el input está dentro del label, y entonces no hace falta for. Las dos formas son válidas.

Cuando se pueden elegir varias, la casilla comparte name y se distingue por su value:

<fieldset>
    <legend>¿Qué tecnologías conoces?</legend>

    <label><input type="checkbox" name="tec" value="html"> HTML</label>
    <label><input type="checkbox" name="tec" value="css"> CSS</label>
    <label><input type="checkbox" name="tec" value="js"> JavaScript</label>
</fieldset>

fieldset y legend agrupan controles que forman una misma pregunta y le ponen nombre al grupo. En un formulario de tres campos sobran; en uno de quince son lo que lo hace navegable, porque cada campo se anuncia precedido del nombre de su grupo. En los botones de opción no resultan prescindibles en la práctica: el legend es lo único que indica sobre qué se está eligiendo.

Los seis fallos de siempre

Antes de mirar código, una prueba que dura treinta segundos y sirve para cualquier web: suelta el ratón. Recorre el formulario con Tab, cambia de opción con las flechas, envía con Enter. Si no sabes dónde está el foco, o hay algo que no puedes alcanzar, ese formulario está roto para todo el que no use un ratón.

Fallo Consecuencia
Campos sin label asociado Se anuncian como «campo de texto», sin nombre
placeholder usado como etiqueta La indicación desaparece al escribir
type="text" para correos, números o fechas Sin validación ni teclado adaptado
Campos sin name El dato no llega al servidor aunque se rellene
<div onclick> como botón de enviar No se alcanza con Tab ni se activa con Enter
<br> para separar los campos El formulario no tiene estructura, solo saltos de línea

Se trabaja

150 minutos · práctica sobre tu propio proyecto y sobre código ajeno

El formulario de contacto se construye en tres fases y cada una se comprueba antes de seguir. Los tres últimos pasos lo someten a la prueba del teclado, a un caso defectuoso y a la mirada de otra persona.

Paso 1 · Fase A · El formulario base · 25 min

En contacto.html, escribe un formulario con:

  • Nombre completo.
  • Correo electrónico.
  • Asunto.
  • Mensaje, con <textarea rows="6">.
  • Botón de envío.

Cada campo con su id y su name, y cada label con su for.

Comprobación inmediata: haz clic sobre el texto de cada etiqueta, no sobre el campo. Si el cursor se desplaza al campo correspondiente, la asociación es correcta. Si no se produce ningún efecto, ese for no coincide con ningún id.

Consultar · textarea y select
<label for="mensaje">Mensaje</label>

<textarea id="mensaje" name="mensaje" rows="6"></textarea>

textarea no es un input: tiene apertura y cierre, y su contenido inicial va entre las dos etiquetas. Cuidado con dejar espacios ahí dentro, porque se convierten en texto escrito.

<label for="motivo">Motivo de contacto</label>

<select id="motivo" name="motivo">
    <option value="">Selecciona una opción</option>
    <option value="informacion">Información</option>
    <option value="soporte">Soporte</option>
    <option value="presupuesto">Presupuesto</option>
</select>

La primera option con value="" sirve para que el desplegable no aparezca ya respondido; combinada con required, obliga a elegir de verdad.

Paso 2 · Fase B · Tipos y validación · 30 min

Amplía tu formulario:

  1. El correo usa type="email" y es obligatorio.
  2. El asunto exige entre 5 y 50 caracteres.
  3. Añade una fecha con type="date".
  4. Añade un campo numérico con min y max que tenga sentido en tu tema.
  5. Añade un <select> con al menos tres opciones y una opción vacía inicial.

Comprobación · tres intentos de envío

Envía el formulario vacío, después con un correo sin arroba, y después con un asunto de tres caracteres. En los tres casos el navegador debe impedir el envío y decirte cuál es el problema, sin recargar la página y sin que tú hayas escrito JavaScript.

Paso 3 · Fase C · Grupos de controles · 30 min

Termina tu formulario con:

  1. Dos botones de opción que compartan name, dentro de un fieldset con su legend.
  2. Un grupo de casillas para selección múltiple.
  3. Una casilla obligatoria de aceptación de condiciones.
  4. Dos fieldset que separen los datos personales de los de la consulta.
<label>
    <input type="checkbox" name="condiciones" required>
    Acepto las condiciones
</label>

Añade además el autocompletado a los campos que lo admiten. Cuesta un atributo y ahorra bastante escritura a quien usa tu web:

<input type="text"  name="nombre" autocomplete="name">
<input type="email" name="email"  autocomplete="email">
Estoy atascado · los radio no funcionan como espero

Si puedes marcar los dos a la vez, es que tienen name distinto. Lo que agrupa unos botones de opción es compartir exactamente el mismo name, y no el fieldset. Lo que sí debe ser distinto en cada uno es el value, que es el dato que se envía, y el id si los asocias con for.

Paso 4 · La prueba del teclado sobre tu formulario · 15 min

Suelta el ratón y recorre tu formulario entero. Anota las respuestas:

  1. ¿Puedes alcanzar todos los controles con Tab, incluido el botón de envío?
  2. ¿Sabes en todo momento dónde está el foco, sin adivinarlo?
  3. ¿El orden en que los recorres es el mismo en que se leen?
  4. ¿Puedes cambiar de opción en los radios con las flechas?
  5. ¿Puedes enviar con Enter sin tocar el ratón?

Cualquier «no» es un defecto que corregir ahora, no una preferencia. Escribe en un comentario qué encontraste y qué cambiaste.

Paso 5 · El formulario defectuoso · 30 min

<form action="/enviar">
    Nombre: <input type="text" placeholder="Escribe tu nombre">
    <br>
    Correo: <input type="text" placeholder="Escribe tu correo">
    <br>
    Edad: <input type="text">
    <br>
    <div class="boton-enviar" onclick="enviar()">Enviar formulario</div>
</form>

5.1 · Ejemplo resuelto

Fallo
El texto «Nombre:» está suelto delante del campo, no es un <label>.
¿Por qué importa, si se lee igual?
Se lee igual para quien ve la pantalla. Para un lector de pantalla no existe ninguna relación entre ese texto y ese campo: anunciará «campo de texto» y habrá que adivinar cuál es.
Corrección
<label for="nombre">Nombre</label> y <input type="text" id="nombre" name="nombre">.

5.2 · Ahora tú

  1. Enumera los seis fallos, en el mismo formato: qué está mal, a quién perjudica y cuál es la corrección.
  2. Reescribe el formulario entero corrigiéndolos todos.
  3. Pásale la prueba del teclado a tu versión.
Ver los seis fallos

1 · Ningún campo tiene <label> asociado: los textos están sueltos delante.

2 · Ningún campo tiene name, así que ningún dato llegaría al servidor.

3 · El correo usa type="text" en lugar de type="email".

4 · La edad usa type="text" en lugar de type="number" con min y max.

5 · El botón es un <div onclick>: no se alcanza con Tab ni se activa con teclado. Debe ser <button type="submit">.

6 · Los campos se separan con <br>, que no aporta estructura. Cada campo con su etiqueta debería ir en su propio bloque.

Paso 6 · Revisión cruzada de formularios · 20 min

Intercambia tu contacto.html con un compañero y audita el suyo sin tocarlo, solo con el navegador y el teclado:

Comprobación Resultado Qué falta
Pulsar el texto de cada etiqueta lleva el foco a su campo
Todos los campos tienen name
Cada campo usa el type que le corresponde
El formulario se recorre entero con Tab
Los radios se agrupan con el mismo name y tienen legend
El envío es un button y funciona con Enter

Devuelve la tabla a su autor. El autor decide qué acepta, y justifica lo que rechace.

Ampliación si has completado el trabajo

Primero termina y comprueba los seis pasos. Los dos retos trabajan con formularios que no has diseñado tú, que es donde fieldset y legend dejan de ser adorno.

Reto 1 · Un formulario de quince campos

Una escuela de música necesita el formulario de inscripción al curso. Solo recibes la lista de datos, sin ningún orden pensado:

Nombre del alumno
Apellidos del alumno
Fecha de nacimiento
Correo de contacto
Teléfono de contacto
Nombre del padre, madre o tutor (solo si el alumno es menor)
Instrumento principal
Nivel declarado: iniciación, medio o avanzado
Años de estudio previos
¿Dispone de instrumento propio?
Días preferidos: lunes, martes, miércoles, jueves, viernes
Franja horaria preferida: mañana o tarde
¿Autoriza el uso de imágenes en redes de la escuela?
Observaciones
Aceptación de las condiciones de matrícula
  1. Agrúpalos en fieldset. Decide cuántos grupos hacen falta y qué legend lleva cada uno. No hay un número correcto, pero sí hay agrupaciones que no se sostienen: tienes que poder decir qué pregunta responde cada grupo.
  2. Elige el type de cada campo. Tres de ellos admiten más de una solución razonable: identifícalos y justifica la que eliges.
  3. Decide cuáles son required. Un formulario que lo exige todo obliga a inventar datos; uno que no exige nada llega vacío. Justifica por escrito los que dejas opcionales.
  4. Resuelve el campo condicional. El nombre del tutor solo se pide si el alumno es menor. Con lo que sabes de HTML no puedes ocultarlo ni mostrarlo, así que decide cómo lo planteas de forma que se entienda igual, y anota qué harías cuando sepas JavaScript.

Escríbelo entero y pásale la prueba del teclado. Con quince campos notarás por primera vez para qué sirve de verdad un legend.

Reto 2 · Un formulario en producción, sin ratón

Busca un formulario real de registro o de compra y recórrelo solo con el teclado, sin enviarlo.

  1. ¿Puedes alcanzar todos los controles con Tab?
  2. ¿Se ve siempre dónde está el foco?
  3. ¿Los campos tienen etiqueta asociada, o solo placeholder? Compruébalo pulsando sobre el texto de la etiqueta.
  4. ¿Qué ocurre si lo envías vacío? ¿El mensaje de error dice qué campo falla, o solo que «hay errores»?
  5. ¿Hay algún campo cuyo type esté mal elegido? Búscalo abriendo un teclado de móvil, o mirando el código con el inspector.

Escribe los tres defectos más graves que encuentres y, para cada uno, la corrección concreta en una línea de HTML. Es un sitio profesional, con equipo detrás: encontrar tres fallos es lo normal, y ese es el dato interesante de este reto.

Objetivo mínimoFormulario completo, etiquetado, con los tipos correctos y recorrible entero con teclado.
Si lo tienesLos seis fallos del formulario defectuoso enumerados y corregidos, y la revisión cruzada contestada.
RetoEl formulario de quince campos agrupado y justificado, y los tres defectos de un formulario en producción con su corrección.

Cierre

5 minutos · comprobación y recuerdo

Lista de verificación de la sesión

  • Tu formulario se recorre entero con Tab, en un orden que tiene sentido.
  • Todos los campos tienen label asociado, id y name.
  • Cada campo usa el type que le corresponde.
  • Los botones de opción comparten name y están dentro de un fieldset con legend.
  • El botón de envío es un <button>.
  • El formulario defectuoso está corregido y la revisión cruzada, devuelta.

Antes de cerrar · 3 minutos, sin mirar

  1. ¿Qué diferencia hay entre id y name?
  2. ¿Por qué un placeholder no sustituye a un label?
  3. Nombra dos cosas que da type="email" y no da type="text".
  4. ¿Por qué la validación nativa no sustituye a la del servidor?
  5. ¿Qué hace que dos botones de opción sean excluyentes?
  6. ¿En qué consiste la prueba del teclado y qué detecta?
Ver respuestas

1 · id identifica el campo dentro del documento y es a lo que apunta el for; name es el nombre con el que el dato se envía al servidor.

2 · Porque desaparece al escribir y deja el campo sin identificar.

3 · Comprueba el formato del correo y ofrece un teclado adaptado en el móvil.

4 · Porque se ejecuta en el navegador y se puede saltar enviando la petición directamente. Ayuda a quien rellena; no protege los datos.

5 · Compartir el mismo atributo name.

6 · Recorrer el formulario sin ratón, solo con Tab, flechas y Enter. Detecta elementos inalcanzables, foco invisible y falsos botones.

Microprueba semanal 5 · 5–10 minutos

Individual, sin IA y sin apuntes.

  1. Escribe un campo de correo con label, id, name y validación nativa.
  2. Explica qué hace que tres radios sean mutuamente excluyentes.
  3. Detecta dos fallos: <label>Edad</label><input type="text" required>.