Product Craft Bible
Multi-select & Bulk Actions
Inicio Datos y Tablas Multi-select & Bulk Actions
Datos y Tablas

Multi-select & Bulk Actions

8 reglas WCAG 2.1 SC 4.1.2 · WAI-ARIA APG Checkbox (Mixed-State) · Adrian Roselli "Check-All / Expand-All Controls" (2024)Shopify Polaris IndexTable (paginatedSelectAllAction) · Helios Design System (HashiCorp) "Table multi-select"Eleken "Bulk action UX" · UX Movement "A better UX approach to bulk actions" · Helios Design System "Table multi-select"WAI-ARIA APG "Developing a Keyboard Interface" (Shift+Space, Shift+Arrow) · WAI-ARIA APG Grid Pattern · UXPin "Keyboard navigation patterns"
77

Multi-select & Bulk Actions

8 reglas
664

Header con checkbox tristate: refleja la selección parcial con estado indeterminado

Cada fila seleccionable lleva un checkbox en la primera columna, y el checkbox del header funciona como control tristate: vacio (ninguna), indeterminado (algunas) y marcado (todas). El estado intermedio no es decorativo, es un requisito de WCAG 4.1.2 (Name, Role, Value): el estado del control de selección masiva debe comunicarse programaticamente. Para un <input type="checkbox"> nativo se usa la propiedad .indeterminate = true vía JS, no aria-checked="mixed" (el estado HTML del checkbox tiene prioridad sobre el ARIA). Para checkboxes ARIA custom (un div con role="checkbox") si se usa aria-checked="mixed". Un header binario que solo alterna marcado/vacio no puede representar la selección de 2 de 5 filas y deja al usuario sin saber el estado real.

WCAG 2.1 SC 4.1.2 · WAI-ARIA APG Checkbox (Mixed-State) · Adrian Roselli "Check-All / Expand-All Controls" (2024)
Preferir
Nombre aria-checked="mixed"
Factura 2041
Factura 2042
Factura 2043
Factura 2044
Factura 2045
Evitar
Nombre vacio (2 marcadas)
Factura 2041
Factura 2042
Factura 2043
Factura 2044
665

Distingue "esta página" de "todos los registros" en dos pasos explicitos

Cuando una tabla página, marcar el header solo selecciona las filas visibles, no los miles de registros del backend. Si el usuario necesita actuar sobre el dataset completo, el sistema ofrece un segundo paso explicito: un banner o link que aparece solo después de seleccionar la página entera, con el conteo real ("Seleccionar los 340 resultados"). Shopify Polaris IndexTable expone esto con un paginatedSelectAllAction separado; Helios documenta tres niveles de alcance (global por bulk selection, página por header, fila por checkbox individual). Este patrón evita operaciones accidentales sobre todo el dataset cuando el usuario solo pretendia actuar sobre la página actual. Mostrar el ratio "3 de 340" ayuda al usuario a entender el alcance real.

Shopify Polaris IndexTable (paginatedSelectAllAction) · Helios Design System (HashiCorp) "Table multi-select"
Preferir
Las 3 facturas de esta página están seleccionadas. Seleccionar las 340 de todas las páginas
Factura 2041
Factura 2042
Factura 2043
3 de 340 seleccionadas · página 1 de 114
Evitar
"Seleccionar todo" aplico la acción a las 340 facturas sin aviso. El usuario solo veia 3.
666

Barra de acciones contextual sticky, con conteo y botón para limpiar

En cuanto el usuario selecciona un item, aparece una barra de acciones contextual flotante y fija al viewport que contiene tres cosas: el conteo ("3 seleccionados"), las acciones disponibles para ese conjunto, y un botón para limpiar la selección. Esta barra no reemplaza la toolbar global, es un layer superpuesto que aparece al seleccionar y desaparece al deseleccionar. Las acciones que no caben van a un menú "Más". Debe mantenerse visible durante el scroll para que en listas largas las acciones sigan accesibles sin volver arriba. La aparición puede animarse (entrada desde abajo) gateada por prefers-reduced-motion y con altura fija para no desplazar el contenido.

Eleken "Bulk action UX" · UX Movement "A better UX approach to bulk actions" · Helios Design System "Table multi-select"
Preferir
Pedido #4810
Pedido #4811
Pedido #4812
Pedido #4813
Pedido #4814
3 seleccionados
Archivar Etiquetar
Evitar
3 seleccionados
Archivar
Eliminar
Pedido #4812
Pedido #4814
Acciones ancladas al header: al hacer scroll quedan fuera del viewport y no se pueden ejecutar.
667

Shift-click para rango, hecho descubrible con hint visible o tooltip

Shift-click selecciona un rango contiguo: ancla en la primera fila marcada y extiende hasta donde cae el click con Shift. El equivalente de teclado es Shift+Espacio (selecciona la fila con foco) y Shift+Flecha (extiende la selección direccionalmente), según el APG. El problema critico de este patrón es su invisibilidad: quien no lo conoce nunca lo descubre y termina marcando 50 filas una por una. El sistema debe comunicarlo activamente: un tooltip en el primer checkbox ("Shift+click para seleccionar rango"), un hint al marcar la primera fila, o una instrucción en el empty state de la barra. Revelar el atajo sobre el elemento mismo es lo que lo hace practicamente accesible, no enterrarlo en documentación.

WAI-ARIA APG "Developing a Keyboard Interface" (Shift+Space, Shift+Arrow) · WAI-ARIA APG Grid Pattern · UXPin "Keyboard navigation patterns"
Preferir
Click para seleccionar · Shift+click para rango
Fila 1ancla
Fila 2
Fila 3
Fila 4
Fila 5Shift+click
Evitar
Fila 1
Fila 2
... 48 clicks más, una por una
Sin hint: el usuario nunca descubre shift-click.
668

Persiste la selección al scrollear y al paginar, o advierte antes de limpiarla

En tablas largas las filas marcadas salen del viewport al scrollear y el usuario pierde la referencia de que tiene seleccionado. La barra sticky con el conteo ("5 seleccionados") resuelve el scroll, pero la paginación exige una decisión explicita: si la selección se mantiene entre páginas, el conteo debe reflejar las páginas no visibles ("5 seleccionados en 2 páginas") y mostrarse como ratio cuando se pueda ("5 de 100"). Si la selección se limpia al paginar, el sistema debe advertirlo antes con un mensaje claro. Limpiar la selección en silencio al cambiar de página genera errores difíciles de detectar: el usuario ejecuta una acción creyendo que aplica a 10 filas cuando aplica a 0.

Helios Design System "Table multi-select" (selected count + ratio, scope global) · Eleken "Bulk action UX"
Preferir
5 seleccionados de 100 · en 2 páginas
1
2
3
Al volver a la página 1, las 3 filas que marcaste allí siguen seleccionadas.
Evitar
"Siguiente página" borro la selección sin aviso. El usuario archiva creyendo que toca 10 filas; toca 0.
669

Calibra la fricción según reversibilidad: modal para permanentes, undo-toast para reversibles

No toda acción en lote merece un modal de confirmación; la sobre-confirmación genera "dialog blindness" y el usuario confirma sin leer. La regla es proporcional al riesgo. Las acciones irreversibles (eliminar permanentemente) piden un modal que comunique las consecuencias y el número exacto de items afectados, con botón de variante peligrosa; GitLab Pajamas llega a exigir escribir el nombre del objeto para borrados de alta severidad. Las acciones reversibles (archivar, mover) pueden ejecutarse de inmediato y ofrecer "Deshacer" en un toast durante 5-10 segundos. Además, los botones destructivos se separan visualmente de los benignos: ponerlos juntos es uno de los errores top de diseño de aplicaciones según NN/g.

GitLab Pajamas "Destructive actions" · Eleken "Bulk action UX" (undo toast) · NN/g "Proximity of consequential options" & "Bulk actions guidelines"
Preferir
Eliminar 12 registros permanentemente
Esta acción no se puede deshacer. Los 12 registros y sus archivos adjuntos se borraran del servidor.
Cancelar
Eliminar 12 items
12 items archivados Deshacer 8s
Evitar
¿Estas seguro?
No
Si
Mismo modal genérico para todo: el usuario lo ignora y confirma sin leer.
670

Progreso granular en ops largas: "124 de 500", sin congelar la UI

Las operaciones en lote sobre muchos registros pueden tardar segundos o minutos, y un spinner genérico "Procesando..." no informa nada y aumenta la ansiedad. El sistema muestra progreso concreto: procesados de total ("124 de 500"), estimación de tiempo restante cuando es posible, y estado por item cuando alguno falla. La UI permanece usable durante el proceso, sin bloquear al usuario. Al terminar se muestra un resumen accionable ("487 exitosos, 13 fallaron - Ver errores") con la opción de recuperar los fallidos. Como criterio: por encima de 2 segundos mostrar progreso, y por encima de 10 segundos ofrecer procesamiento en background con notificación al completar.

LogRocket "UI patterns for async workflows" ("124 of 500 rows", ciclo Queued→Running→Success→Failed) · Eleken "Bulk action UX" (feedback multinivel)
Preferir
Publicando registros 124 / 500
Paso 4 de 6~30s restantes
487 exitosos 13 con error Ver errores
Evitar
Procesando, por favor espere...
Sin progreso, sin tiempo, UI bloqueada.
671

Triada ARIA: aria-multiselectable en el grid, aria-selected por fila, conteo en live region

La accesibilidad de multi-select requiere tres capas ARIA coordinadas. (1) aria-multiselectable="true" en el elemento con role="grid" indica que permite selección multiple. (2) aria-selected="true/false" en cada fila o gridcell comunica su estado. (3) Un nodo con role="status" o aria-live="polite" anuncia el conteo actualizado cada vez que cambia la selección, para que el usuario de lector de pantalla sepa cuantos items tiene sin navegar a la toolbar. La region live debe estar vacia al cargar la página y poblarse por JS solo cuando hay algo que anunciar; polite retrasa el anuncio hasta que el lector termina la frase en curso. Marcar la fila solo con una clase CSS .selected sin aria-selected deja al usuario de teclado sin saber que tiene seleccionado.

WAI-ARIA APG Grid Pattern (aria-multiselectable, aria-selected) · Helios Design System (role="status" en el conteo) · Sara Soueidan "Accessible notifications with ARIA live regions"
Preferir
multiselect.html
<!-- 1. El grid permite selección multiple -->
<table role="grid" aria-multiselectable="true">
  <!-- 2. Cada fila declara su estado -->
  <tr aria-selected="true"> ... </tr>
  <tr aria-selected="false"> ... </tr>
</table>

<!-- 3. Live region: vacia al cargar,
     poblada por JS al cambiar selección -->
<span role="status" aria-live="polite">
  3 filas seleccionadas
</span>
Evitar
<tr class="selected"> ... </tr>
// solo color de fondo, sin ARIA
Sin aria-selected ni live region: el lector de pantalla no detecta el cambio y el usuario no sabe que tiene seleccionado.