A 04-01 vam veure que els controls natius de formulari (<input>, <select>, <textarea>, <button>) porten rol, focus i operació per teclat de fàbrica. Però un formulari accessible necessita quelcom més que bons controls: necessita que cada control tingui nom, que els camps relacionats estiguin agrupats, que les ajudes i els errors arribin a tothom i que l'ordre de focus sigui lògic. Aquests són precisament els criteris 3.3.x ("Ajuda a l'entrada de dades") que al Mòdul 3 vam estudiar com a requisit i que ara implementem.
El terreny de pràctica és el formulari de matrícula/registre de Cursalia: nom, correu, telèfon, contrasenya, nivell d'estudis, interessos i acceptació de condicions. L'anirem construint peça a peça, sempre amb exemples correctes davant d'incorrectes, pensant en la Marta (NVDA li ha de llegir l'etiqueta de cada camp), l'Hugo (necessita instruccions clares i sense ambigüitat) i la Lucía (el recorre sencer amb el teclat).
Contingut
- L'etiqueta: associar
labeli control - Per què el placeholder no és una etiqueta
- Agrupar amb
fieldsetilegend - Camps obligatoris:
requirediaria-required - Instruccions i ajudes amb
aria-describedby - Errors accessibles (i una primera ullada a
role="alert") autocompletei tipus d'input- Focus i ordre lògic
- Errors comuns i consells
- Exercicis
- Conclusió
L'etiqueta: associar label i control
Tot control de formulari ha de tenir una etiqueta accessible. L'etiqueta és el name del control (recorda 4.1.2): sense ella, la Marta sent "quadre d'edició" sense saber què escriure. Hi ha dues maneres correctes d'associar un <label> amb el seu control.
Opció A — for / id (la més comuna i flexible): l'atribut for del label apunta a l'id del control.
<!-- CORRECTE: for apunta a l'id -->
<label for="email">Correu electrònic</label>
<input type="email" id="email" name="email">Opció B — label envoltant: el control va dins del <label>. No cal for/id.
<!-- CORRECTE: el label envolta el control -->
<label>
Correu electrònic
<input type="email" name="email">
</label>Totes dues fan que en prémer sobre el text s'enfoqui el camp (àrea de clic més gran, cosa que agraeix la Lucía) i que el lector l'anunciï amb el seu nom. L'error habitual és no associar-los: text solt al costat d'un input.
<!-- INCORRECTE: el text no està associat al camp -->
<span>Correu electrònic</span>
<input type="email" name="email">Aquí no hi ha <label> (o el seu for no coincideix amb cap id): la Marta sent "quadre d'edició en blanc". Si per disseny no pots mostrar un text visible (per exemple, el cercador del catàleg amb només una lupa), utilitza aria-label, que veurem a 04-03:
Per què el placeholder no és una etiqueta
És temptador posar només un placeholder="Correu electrònic" i estalviar-se el <label>. És un error seriós per diversos motius:
| Problema del placeholder com a etiqueta | A qui afecta |
|---|---|
| Desapareix en començar a escriure; ja no saps què era aquell camp | Hugo (memòria/atenció), tothom |
| El seu contrast sol ser baix (gris clar) i no compleix 1.4.3 | Sofía (baixa visió) |
| Molts lectors no l'anuncien com a nom fiable | Marta |
| No es pot prémer per enfocar el camp | Lucía |
<!-- INCORRECTE: només placeholder, sense label -->
<input type="email" placeholder="Correu electrònic">
<!-- CORRECTE: label visible; el placeholder, si de cas, només com a exemple de format -->
<label for="email">Correu electrònic</label>
<input type="email" id="email" placeholder="[email protected]">Regla: el placeholder és, com a molt, una pista de format; mai substitueix l'etiqueta.
Agrupar amb fieldset i legend
Quan diversos controls formen un sol grup conceptual —un conjunt de radios (tria'n un) o de checkboxes (tria'n diversos)—, cal envoltar-los en <fieldset> i donar-los un títol comú amb <legend>. Així el lector anuncia el grup ("Nivell d'estudis, grup") abans de cada opció, donant el context que un radio solt no tindria.
<!-- CORRECTE: grup de radios amb fieldset/legend -->
<fieldset>
<legend>Nivell d'estudis</legend>
<label><input type="radio" name="nivel" value="secundaria"> Secundària</label>
<label><input type="radio" name="nivel" value="grado"> Grau universitari</label>
<label><input type="radio" name="nivel" value="master"> Màster o superior</label>
</fieldset>La Marta sent: "Nivell d'estudis, grup. Secundària, botó de radio, 1 de 3". Sense el fieldset, sentiria tres radios solts sense saber a quina pregunta responen.
<!-- INCORRECTE: un text solt no agrupa res semànticament -->
<p>Nivell d'estudis</p>
<label><input type="radio" name="nivel" value="grado"> Grau</label>El mateix aplica als interessos (checkboxes) o a l'acceptació de diverses condicions. No abusis del fieldset per a camps independents (nom, correu): és només per a grups.
Camps obligatoris: required i aria-required
Marcar un camp com a obligatori té dues cares que cal cobrir alhora: la funcional i la comunicativa.
required: atribut natiu. Activa la validació del navegador i exposa l'estat "requerit" al lector (la Marta sent "requerit"). És la primera opció.aria-required="true": només si necessites comunicar l'obligatorietat sense activar la validació nativa del navegador. No dupliquis tots dos sense motiu; ambrequirednormalment n'hi ha prou.- Indicació visual i textual: no n'hi ha prou amb un asterisc vermell (falla 1.4.1, "no només per color/forma"). Explica què significa l'asterisc i, encara millor, reforça'l amb text.
<!-- CORRECTE: obligatori comunicat de tres maneres: nativa, visual i textual -->
<p id="leyenda-oblig">Els camps marcats amb * són obligatoris.</p>
<label for="nombre">Nom complet *</label>
<input type="text" id="nombre" name="nombre" required
aria-describedby="leyenda-oblig"><!-- INCORRECTE: l'obligatorietat viu només al color de l'asterisc -->
<label for="nombre">Nom complet <span style="color:red">*</span></label>
<input type="text" id="nombre"> <!-- ni required, ni explicació del * -->Instruccions i ajudes amb aria-describedby
Molts camps necessiten una ajuda: format de la contrasenya, per a què s'usa el telèfon, etc. Perquè aquesta ajuda es llegeixi juntament amb el camp (i no quedi com a text orfe al costat), s'associa amb aria-describedby, que apunta a l'id del text d'ajuda. El lector llegeix primer l'etiqueta i després la descripció.
<!-- CORRECTE: l'ajuda s'associa amb aria-describedby -->
<label for="pass">Contrasenya</label>
<input type="password" id="pass" name="pass"
aria-describedby="ayuda-pass" required>
<p id="ayuda-pass">Mínim 8 caràcters, amb una lletra i un número.</p>La Marta sent: "Contrasenya, quadre d'edició, requerit. Mínim 8 caràcters, amb una lletra i un número". L'Hugo llegeix aquesta mateixa instrucció clara abans d'escriure, no després de fallar. aria-describedby pot apuntar a diversos ids separats per espais (per exemple, l'ajuda i el missatge d'error del mateix camp).
<!-- INCORRECTE: l'ajuda existeix visualment però no està associada -->
<label for="pass">Contrasenya</label>
<input type="password" id="pass">
<p>Mínim 8 caràcters, amb una lletra i un número.</p> <!-- sense aria-describedby: el lector no la vincula -->Errors accessibles (i una primera ullada a role="alert")
Quan l'usuari s'equivoca, el criteri 3.3.1 exigeix identificar l'error en text (no només amb color) i descriure'l de manera útil. Bones pràctiques:
- Text explícit, no només una vora vermella: "El correu no és vàlid: falta la @".
- Associat al camp amb
aria-describedby, perquè el lector el llegeixi en enfocar aquell camp. - No dependre del color: afegeix una icona o la paraula "Error" (això és 1.4.1, que veurem a fons a 04-04).
- Moure el focus al primer camp amb error o resumir els errors al principi ajuda la Lucía i la Marta.
<!-- CORRECTE: error en text, associat, i no només per color -->
<label for="email">Correu electrònic *</label>
<input type="email" id="email" name="email" required
aria-describedby="err-email" aria-invalid="true">
<p id="err-email" class="error">
<span aria-hidden="true">⚠️</span> Error: el correu ha d'incloure una @.
</p>Aquí aria-invalid="true" marca el camp com a invàlid a l'arbre d'accessibilitat, i aria-describedby fa que el missatge es llegeixi en enfocar-lo.
Quan l'error apareix de forma dinàmica (després de prémer "Envia", sense recarregar la pàgina), cal a més anunciar-lo encara que el focus estigui en un altre lloc. Per a això existeixen les live regions. Una introducció breu: un contenidor amb role="alert" (o aria-live="assertive") fa que el lector llegeixi automàticament el text que aparegui a dins, sense que l'usuari hagi d'anar a buscar-lo.
<!-- El lector anunciarà el text tan bon punt el teu JS l'insereixi aquí -->
<div role="alert" id="resumen-errores"></div>// En validar, insereixes el missatge i NVDA el llegeix sol:
document.getElementById('resumen-errores').textContent =
'Revisa el formulari: hi ha 2 camps amb errors.';Ens quedem en aquesta introducció: el detall d'aria-live, role="status" vs role="alert" i les regions dinàmiques es veu a nivell de marcatge a 04-03, i la validació completa amb JavaScript (quan validar, com moure el focus, actualitzar missatges en viu) pertany al Mòdul 5.
autocomplete i tipus d'input
Triar bé el type i afegir autocomplete redueix l'esforç de tothom, i de manera molt notable per a la Lucía (menys teclejar), l'Hugo (menys per recordar) i qui utilitza control per veu o mòbil.
| Camp | type correcte |
autocomplete |
Benefici |
|---|---|---|---|
| Correu | email |
email |
Teclat amb @, validació de format, autoemplenat |
| Telèfon | tel |
tel |
Teclat numèric al mòbil |
| Nom | text |
name |
Autoemplenat del nom desat |
| Contrasenya (registre) | password |
new-password |
El gestor en suggereix una de segura |
<!-- CORRECTE: tipus i autocomplete que ajuden el navegador i la persona -->
<label for="email">Correu electrònic</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="tel">Telèfon</label>
<input type="tel" id="tel" name="tel" autocomplete="tel">autocomplete compleix a més el criteri 1.3.5 ("Identificar el propòsit de l'entrada"), que ajuda eines que adapten o emplenen camps per l'usuari.
Focus i ordre lògic
L'ordre en què la Lucía arriba als camps en prémer Tab ha de seguir l'ordre visual i lògic del formulari. La clau és que l'ordre del DOM coincideixi amb l'ordre visual: si maquetes de dalt a baix a l'HTML, el focus anirà bé sense trucs.
- No utilitzis
tabindexpositiu (tabindex="1","2"...): trenca l'ordre natural i és una font constant de bugs. Deixa que l'ordre el dicti el DOM. - Un
<button type="submit">real permet enviar el formulari amb Enter des de qualsevol camp. - Després d'enviar amb errors, portar el focus al resum d'errors o al primer camp fallit estalvia a la Lucía tabular a cegues (implementació amb JS: Mòdul 5).
<!-- INCORRECTE: tabindex positiu força un ordre artificial i fràgil -->
<input id="a" tabindex="3">
<input id="b" tabindex="1">
<input id="c" tabindex="2"><!-- CORRECTE: sense tabindex; l'ordre del DOM ja és l'ordre de focus -->
<input id="a">
<input id="b">
<input id="c">Errors Comuns i Consells
- Camps sense
<label>associat. És la fallada número u. Cada control necessita nom perfor/id, label envoltant o, en darrer cas,aria-label. - Utilitzar el placeholder com a etiqueta. Desapareix, contrasta malament i no és fiable com a nom. Posa sempre etiqueta visible.
- Obligatorietat només per color. L'asterisc vermell no n'hi ha prou: afegeix
requiredi explica què significa el*. - Ajudes i errors sense associar. Un text d'ajuda al costat del camp que no està enllaçat amb
aria-describedbyés invisible per al lector. - Errors només amb vora vermella. La Marta no veu el color: l'error ha d'anar en text i estar associat.
- Radios/checkboxes sense
fieldset/legend. Perden el context de la pregunta. tabindexpositiu. Gairebé mai és la solució; ordena el DOM.- Consell: emplena el teu formulari sencer només amb el teclat i amb un lector, amb els ulls tancats. Si no saps què demana un camp o on és l'error, encara no és accessible.
Exercicis
Exercici 1: donar nom als camps
Aquest fragment del registre de Cursalia no associa etiquetes. Corregeix-lo utilitzant for/id i substitueix el placeholder-etiqueta per una etiqueta real.
Exercici 2: agrupar els interessos
Els usuaris poden marcar diversos interessos. Marca correctament aquest grup de checkboxes perquè el lector anunciï el títol del grup.
<p>Interessos</p>
<label><input type="checkbox" name="int" value="dev"> Desenvolupament</label>
<label><input type="checkbox" name="int" value="ux"> Disseny UX</label>
<label><input type="checkbox" name="int" value="data"> Dades</label>Exercici 3: un camp amb ajuda i error accessibles
Escriu el camp "Contrasenya": etiqueta visible, obligatori, amb una ajuda de format associada i un missatge d'error (associat i no dependent només del color) per a quan la contrasenya sigui massa curta.
Solucions
Solució 1. Cada control rep una etiqueta associada; el placeholder deixa pas a un <label> real:
<label for="nombre">Nom</label>
<input type="text" id="nombre" name="nombre" autocomplete="name">
<label for="email">Correu electrònic</label>
<input type="email" id="email" name="email" autocomplete="email">Solució 2. El grup sencer va en un <fieldset> amb <legend>:
<fieldset>
<legend>Interessos</legend>
<label><input type="checkbox" name="int" value="dev"> Desenvolupament</label>
<label><input type="checkbox" name="int" value="ux"> Disseny UX</label>
<label><input type="checkbox" name="int" value="data"> Dades</label>
</fieldset>Ara el lector anuncia "Interessos, grup" abans de cada casella.
Solució 3. Etiqueta visible + required + ajuda amb aria-describedby + error en text associat, amb icona a més del color:
<label for="pass">Contrasenya *</label>
<input type="password" id="pass" name="pass" required
autocomplete="new-password"
aria-describedby="ayuda-pass err-pass"
aria-invalid="true">
<p id="ayuda-pass">Mínim 8 caràcters, amb una lletra i un número.</p>
<p id="err-pass" class="error">
<span aria-hidden="true">⚠️</span> Error: la contrasenya és massa curta.
</p>Fixa't que aria-describedby apunta a dos ids (ajuda i error), així el lector llegeix tots dos. Quan la contrasenya sigui vàlida, el teu codi traurà aria-invalid i el missatge d'error (això ja és lògica de JS, Mòdul 5).
Conclusió
Un formulari accessible no és màgia: és etiquetar cada control, agrupar el que va junt, comunicar l'obligatorietat i les ajudes per text associat, descriure els errors de manera clara i no només per color, i respectar l'ordre de focus del DOM. Amb això, el formulari de matrícula de Cursalia deixa de ser un mur per a la Marta, l'Hugo i la Lucía. Ens hem recolzat gairebé en tot en HTML natiu i només hem tret el cap a aria-describedby, aria-invalid i role="alert".
Aquests atributs aria-* són la porta a la següent lliçó. A 04-03 · Ús de Rols i Propietats ARIA farem un pas enrere per entendre què és ARIA, les seves cinc regles d'or (començant per "no l'utilitzis si hi ha HTML natiu"), com es calcula el nom accessible que hem anat utilitzant (aria-label, aria-labelledby, aria-describedby) i com descriure estats com aria-expanded al desplegable de filtres de Cursalia, sempre a nivell de marcatge.
Curs d'Accessibilitat Web
Mòdul 1: Introducció a l'Accessibilitat Web
- Què és l'Accessibilitat Web?
- La Importància de l'Accessibilitat Web
- Visió General de les Lleis i Normes d'Accessibilitat
- Introducció a les WCAG
Mòdul 2: Comprensió de les Discapacitats i les Tecnologies d'Assistència
- Tipus de Discapacitats
- Visió General de les Tecnologies d'Assistència
- Com Utilitzen la Web les Persones amb Discapacitat
Mòdul 3: Principis del Disseny Accessible
- Perceptible: Fer el Contingut Disponible als Sentits
- Operable: Interfície d'Usuari i Navegació
- Comprensible: Informació i Operació
- Robust: Compatibilitat amb Tecnologies Actuals i Futures
Mòdul 4: Implementació de l'Accessibilitat en HTML i CSS
- HTML Semàntic
- Formularis Accessibles
- Ús de Rols i Propietats ARIA
- Contrast de Color i Redimensionament del Text
Mòdul 5: Accessibilitat en JavaScript i Multimèdia
- Creació de Widgets de JavaScript Accessibles
- Accessibilitat del Teclat
- Contingut de Vídeo i Àudio Accessible
- Proporcionar Alternatives de Text per a les Imatges
Mòdul 6: Proves i Avaluació de l'Accessibilitat
- Tècniques de Proves Manuals
- Eines de Proves Automatitzades
- Proves d'Usuari amb Tecnologies d'Assistència
- Interpretació d'Informes d'Accessibilitat
