Addaw, ir a inicio Donar

Lección 7 de 10 · 12 min de lectura · Revisado el 26/10/2026

Casillas, interruptores, archivos y otros controles especiales

Un formulario real no está hecho solo de cuadros de texto. Tiene casillas para aceptar condiciones, interruptores de preferencias, un botón para adjuntar el certificado de empadronamiento, un deslizador para elegir un importe, un contador de caracteres en el campo de observaciones. Cada uno de estos controles tiene su semántica, su comportamiento de teclado y sus anuncios, y cada uno se rompe de una manera característica cuando se rediseña. En esta lección los repasamos con la misma regla que aplicaremos a los selects y calendarios en la lección siguiente: primero el control nativo, y solo si no basta, el patrón de la ARIA Authoring Practices Guide (se abre en una ventana nueva) (APG) completo. Los componentes interactivos genéricos (menús, pestañas, diálogos) se tratan en el curso N3-C5.

Casillas y botones de opción con estilo propio

La petición más habitual del diseño es «quiero que las casillas tengan nuestro estilo». La respuesta accesible casi nunca es construirlas con <div>: es conservar el <input> nativo y vestirlo. Hay tres formas, de menos a más trabajo:

  1. accent-color: una sola propiedad CSS que cambia el color de casillas, botones de opción, deslizadores y barras de progreso nativos. Funciona en todos los navegadores actuales y no toca la semántica.
  2. appearance: none sobre el propio <input> y dibujo del estado con CSS. El control sigue siendo nativo: recibe el foco, responde a la barra espaciadora y anuncia «marcado» o «no marcado».
  3. Input visualmente oculto (con la técnica de ocultación visual, nunca con display: none ni visibility: hidden) y un elemento decorativo al lado que se pinta según :checked y :focus-visible del input.

Ejemplo

Incorrecto: una casilla que solo existe para el ratón.

<div class="check" onclick="this.classList.toggle('on')"></div>
<span>Acepto la política de privacidad</span>

Correcto: nativa, con el color corporativo y la etiqueta asociada.

<input type="checkbox" id="privacidad" name="privacidad" required>
<label for="privacidad">He leído y acepto la
  <a href="/privacidad">política de privacidad</a> (obligatorio)</label>

<style> input[type="checkbox"] { accent-color: #1a4fa0; } </style>

Recuerda el comportamiento de teclado que dan gratis los controles nativos y que cualquier sustituto debe reproducir: en las casillas, Tab va de una a otra y la barra espaciadora marca o desmarca; en los botones de opción, Tab entra en el grupo (en la opción marcada o en la primera) y las flechas mueven la selección dentro del grupo. Los botones de opción se agrupan por su atributo name: si cada uno tiene un name distinto, dejan de comportarse como grupo.

Grupos de casillas obligatorios

El atributo required en una casilla significa que esa casilla debe marcarse; no sirve para «elige al menos una opción». Para ese caso, indica la regla en la <legend> («Idiomas en los que puedes atender (elige al menos uno)»), valida con JavaScript y, si falla, muestra el mensaje junto al grupo y vincúlalo al <fieldset> con aria-describedby. Como el soporte de las descripciones en grupos varía entre lectores, el resumen de errores de la lección de validación sigue siendo la red de seguridad.

Interruptores

Un interruptor (switch) representa algo que está activado o desactivado y que surte efecto de inmediato, como «Recibir avisos por SMS» en el área personal. Una casilla, en cambio, es una elección que se envía con el formulario. Si el control aplica el cambio al pulsarlo, usa el rol switch:

<button type="button" role="switch" aria-checked="false" id="sms">
  Avisos por SMS
</button>
<p role="status" id="sms-estado"></p>

<script>
  const sw = document.getElementById('sms');
  sw.addEventListener('click', () => {
    const on = sw.getAttribute('aria-checked') === 'true';
    sw.setAttribute('aria-checked', String(!on));
    document.getElementById('sms-estado').textContent =
      !on ? 'Avisos por SMS activados' : 'Avisos por SMS desactivados';
  });
</script>

El lector anuncia «Avisos por SMS, interruptor, desactivado». El texto del botón no cambia: lo que cambia es el estado. La confirmación en role="status" es opcional, pero útil cuando el cambio se guarda en el servidor. Visualmente, muestra el estado también con texto («Sí» / «No») o con la posición del mando, no solo con el color. El patrón completo está en la APG.

Error frecuente

Un botón que cambia su texto de «Activar avisos» a «Desactivar avisos» y además lleva aria-pressed. El lector anuncia «Desactivar avisos, botón de alternancia, pulsado» y nadie sabe si los avisos están activos o no. Elige una sola estrategia: o un nombre fijo con estado (role="switch" o aria-pressed), o un texto que cambia sin estado.

Adjuntar archivos

En las sedes electrónicas, adjuntar documentos es parte de casi cualquier trámite, y es uno de los puntos donde más solicitudes se abandonan. El <input type="file"> nativo es accesible con teclado y lector de pantalla; los problemas llegan con las zonas de «arrastra aquí tus archivos».

  • Arrastrar no puede ser la única vía: debe haber un botón de selección que funcione con teclado (2.1.1 Teclado) y, por 2.5.7 Movimientos de arrastre (AA), una alternativa con un solo puntero sin arrastre.
  • El atributo accept filtra el selector de archivos, pero no explica nada. Los formatos y el tamaño máximo se escriben en la ayuda del campo y se asocian con aria-describedby.
  • El progreso de la subida se muestra con <progress> etiquetado y se anuncia en pocos hitos (inicio, final, error) con una región de estado, no en cada porcentaje.
  • Cada archivo subido aparece en una lista con su nombre y un botón «Eliminar» que diga cuál elimina.
  • Los errores son concretos: «El archivo pesa 12 MB y el máximo es 10 MB», no «Archivo no válido».
<label for="padron">Certificado de empadronamiento</label>
<p id="padron-ayuda">PDF, JPG o PNG. Máximo 10 MB.</p>
<input type="file" id="padron" name="padron"
       accept=".pdf,.jpg,.jpeg,.png" aria-describedby="padron-ayuda">

<ul id="adjuntos">
  <li>padron-2026.pdf (240 kB)
    <button type="button">Eliminar <span class="visualmente-oculto">padron-2026.pdf</span></button>
  </li>
</ul>
<p role="status" id="adjuntos-estado"></p>

Deslizadores, cantidades y contadores

Deslizadores

El <input type="range"> nativo es operable con flechas, pero anuncia números sin contexto («25000»). Añade aria-valuetext con el valor legible («25.000 euros») y actualízalo al cambiar. Y como afinar un valor exacto con un deslizador es difícil para muchas personas, en importes o plazos ofrece al lado un campo de texto sincronizado. Un deslizador hecho a mano exige el patrón slider de la APG, con aria-valuenow, aria-valuemin y aria-valuemax.

Selectores de cantidad

Los botones «−» y «+» del carrito necesitan nombre completo («Reducir cantidad de Mochila escolar azul»), un objetivo de al menos 24 × 24 píxeles y, tras pulsarlos, un anuncio del nuevo valor en una región de estado si el foco no está sobre el campo. Permite también escribir la cantidad directamente.

Contadores de caracteres

Indica el límite en la descripción del campo («Máximo 500 caracteres») y muestra lo que queda. No anuncies cada pulsación: actualiza el texto visible al momento, pero envía el anuncio a la región activa con un pequeño retardo, cuando la persona deja de escribir, o al cruzar umbrales. Evita también que maxlength recorte en silencio un texto pegado: es preferible dejar escribir de más y mostrar un error claro con lo que sobra.

Punto clave

Cada control especial tiene un control nativo que ya resuelve la mayor parte del trabajo: checkbox, radio, file, range, <progress>. Tu trabajo es vestirlo, explicarlo (formatos, límites, valor legible) y anunciar con mesura lo que cambia sin mover el foco.

Pruébalo tú

Busca un formulario con casillas o interruptores personalizados (las preferencias de cookies de muchas webs son un buen candidato). Recórrelo solo con teclado: ¿llegas a cada control con Tab? ¿La barra espaciadora cambia el estado? Inspecciona uno en el panel Accessibility del navegador: ¿qué rol y qué estado expone?

Resumen

  • Para dar estilo a casillas y botones de opción, conserva el input nativo: accent-color, appearance: none u ocultación visual, nunca display: none.
  • required en una casilla obliga a marcar esa casilla; «al menos una» se valida por script y se explica en la <legend>.
  • Un interruptor de efecto inmediato usa role="switch" con aria-checked y un nombre que no cambia.
  • Subir archivos exige botón además de arrastre, formatos y tamaño explicados, progreso anunciado con mesura y errores concretos.
  • Deslizadores con aria-valuetext y campo alternativo; contadores que no anuncian cada tecla ni recortan en silencio.

Comprueba lo que has aprendido

Esta lección se puede leer libremente. Para hacer su test de 3 preguntas, los ejercicios prácticos con páginas reales, guardar tu avance y optar al examen y al certificado, matricúlate en el curso.