Tengo una lista de datos en una visualización y quiero que sea lo más accesible posible. Hay dos listas una al lado de la otra.
Los elementos de la lista tienen dos estados. Varias filas pueden estar activas o inactivas. Se puede seleccionar una sola fila.
Al seleccionar algo en una lista, se mostrarán los elementos 'relacionados' como activos e inactivos. Vea el ejemplo simplificado a continuación. El usuario seleccionó "A 2", que está vinculado a "B 1" y "B 4", por lo que A2 está aria-selected
pero no hay aria-active
o aria-inactive
, pensé en usar aria-disabled
como se demostró: ¿PERO esto no indica que no se puede interactuar? El usuario aún puede hacer clic en el elemento deshabilitado para luego seleccionarlo.
¿Sería mejor hacer múltiples aria-selected
y un solo aria-current=true
en el único elemento 'seleccionado'? ¿Sería extraño que si el usuario aún no ha hecho una selección y todo está 'activo', todos los elementos serán aria-selected=true
?
.container { display: grid; grid-template-columns: 200px 200px } .list div { margin: 0.5rem; background: grey; } .list .selected { background: red; } .list .inactive { opacity: 0.3; }
<div class="container"> <div class="list"> <div role="row">A 1</div> <div role="row" class="selected" aria-selected="true">A 2</div> <div role="row">A 3</div> <div role="row">A 4</div> </div> <div class="list"> <div role="row">B 1</div> <div role="row" class="inactive" aria-disabled="true">B 2</div> <div role="row" class="inactive" aria-disabled="true">B 3</div> <div role="row">B 4</div> </div> </div>
Aclaraciones de comentarios. Así es como se ve la implementación real con un elemento seleccionado: , es bastante complejo, así que traté de desglosarlo hasta el problema específico.
role="presentation"
. En realidad, hay una línea de tiempo en el medio, y las cosas solo están 'activas' si los elementos en la línea de tiempo comparten el elemento en la otra lista. No había considerado una aria-label
, creo que podría ser la mejor solución porque el usuario puede hacer clic y seleccionar el elemento inactivo.
Desde el punto de vista de la semántica y la accesibilidad, consideraría separar los controles de la visualización en sí, al menos en términos de marcado. Esto le permitirá usar más elementos de entrada semánticos, como <input type="radio">
, que vienen con sus propios estados que la tecnología de asistencia entiende. Y mantendrá el estado de selección (que es un rasgo de elemento de entrada) separado del estado resaltado (que es un rasgo de visualización).
A continuación, puede vincular esos elementos de entrada a la visualización mediante el atributo aria-controls
.
Para indicar el estado de los elementos en la visualización en sí, en lugar de usar roles ARIA, sugeriría usar solo texto.
Un ejemplo básico podría funcionar algo como esto:
const items = document.querySelectorAll('.item') document.querySelectorAll('input').forEach(input => { input.addEventListener('change', e => { let targets = e.target.dataset.controls.split(' ') items.forEach(item => { let index = targets.indexOf(item.id) if (-1 === index) item.dataset.state = 'inactive' else if (0 === index) item.dataset.state = 'selected' else item.dataset.state = 'highlighted' }) }) })
body { display: grid; grid-template: auto auto / auto auto; grid-auto-flow: column; } form, figure { display: grid; grid-template: auto/auto auto 1fr; gap: 1ch; } fieldset { display: grid; grid-template: auto/auto auto; } input, label { cursor: pointer; } figure, ul { padding: 0; margin: 0; } ul { list-style: none; } li { margin: 1ch 0 0 1ch; padding: 1ch; } li[data-state="inactive"] { background: #ddd; } li[data-state="selected"] { background: #aaffaa; } li[data-state="highlighted"] { background: #aaaaff; } li[data-state="inactive"] span, li[data-state="highlighted"] span.selected { display: none; } li[data-state="selected"] span, li[data-state="highlighted"] span.highlighted { display: inline; }
<h2>Controls</h2> <form> <fieldset> <legend>List A</legend> <input id="input-A1" type="radio" name="input" aria-controls="graphic" data-controls="item-A1 item-B2 item-B3" /> <label for="input-A1">A1</label> <input id="input-A2" type="radio" name="input" aria-controls="graphic" data-controls="item-A2 item-B1" /> <label for="input-A2">A2</label> <input id="input-A3" type="radio" name="input" aria-controls="graphic" data-controls="item-A3 item-B2 item-B3 item-B4" /> <label for="input-A3">A3</label> <input id="input-A4" type="radio" name="input" aria-controls="graphic" data-controls="item-A4 item-B2" /> <label for="input-A4">A4</label> </fieldset> <fieldset> <legend>List B</legend> <input id="input-B1" type="radio" name="input" aria-controls="graphic" data-controls="item-B1 item-A2 item-A3 item-A4" /> <label for="input-B1">B1</label> <input id="input-B2" type="radio" name="input" aria-controls="graphic" data-controls="item-B2 item-A2 item-B3" /> <label for="input-B2">B2</label> <input id="input-B3" type="radio" name="input" aria-controls="graphic" data-controls="item-B3 item-A1 item-A4" /> <label for="input-B3">B3</label> <input id="input-B4" type="radio" name="input" aria-controls="graphic" data-controls="item-B4 item-A2" /> <label for="input-B4">B4</label> </fieldset> </form> <h2>Visualization</h2> <figure id="graphic" aria-live="polite"> <ul aria-label="List A"> <li class="item" id="item-A1" data-state="inactive">A1<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-A2" data-state="inactive">A2<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-A3" data-state="inactive">A3<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-A4" data-state="inactive">A4<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> </ul> <ul aria-label="List B"> <li class="item" id="item-B1" data-state="inactive">B1<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-B2" data-state="inactive">B2<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-B3" data-state="inactive">B3<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> <li class="item" id="item-B4" data-state="inactive">B4<span class="selected">, selected</span><span class="highlighted">, highlighted</span></li> </ul> </figure>
Pero probablemente desee que la visualización sea directamente manipulable haciendo clic en los elementos mismos. Si es así, podría mantener la misma estructura que el primer ejemplo, pero ocultar visualmente los controles y el texto accesibles, y activar los controles cada vez que un usuario haga clic en un elemento de la visualización.
Nuevamente, esto todavía tiene los controles de formulario que son responsables de controlar el estado; simplemente están ocultos visualmente:
const items = document.querySelectorAll('.item') document.querySelectorAll('input').forEach(input => { input.addEventListener('change', e => { let targets = e.target.dataset.controls.split(' ') items.forEach(item => { let index = targets.indexOf(item.id) if (-1 === index) item.dataset.state = 'inactive' else if (0 === index) item.dataset.state = 'selected' else item.dataset.state = 'highlighted' }) }) }) items.forEach(item => { item.addEventListener('click', e => { let inputId = 'input-' + e.target.id.split('-')[1] document.getElementById(inputId).click() }) })
.sr-only { border: 0; clip: rect(1px, 1px, 1px, 1px); -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; white-space: nowrap; } figure { display: grid; grid-template: auto/auto auto 1fr; gap: 1ch; } figure, ul { padding: 0; margin: 0; } ul { list-style: none; } li { margin: 1ch 0 0 1ch; padding: 1ch; cursor: pointer; } li:hover { opacity: 0.6; } li[data-state="inactive"] { background: #ddd; } li[data-state="selected"] { background: #aaffaa; } li[data-state="highlighted"] { background: #aaaaff; } li[data-state="inactive"] span, li[data-state="highlighted"] span.selected { display: none; } li[data-state="selected"] span, li[data-state="highlighted"] span.highlighted { display: inline; }
<div class="sr-only"> <h2>Controls</h2> <form> <fieldset> <legend>List A</legend> <input id="input-A1" type="radio" name="input" aria-controls="graphic" data-controls="item-A1 item-B2 item-B3"/> <label for="input-A1">A1</label> <input id="input-A2" type="radio" name="input" aria-controls="graphic" data-controls="item-A2 item-B1"/> <label for="input-A2">A2</label> <input id="input-A3" type="radio" name="input" aria-controls="graphic" data-controls="item-A3 item-B2 item-B3 item-B4"/> <label for="input-A3">A3</label> <input id="input-A4" type="radio" name="input" aria-controls="graphic" data-controls="item-A4 item-B2"/> <label for="input-A4">A4</label> </fieldset> <fieldset> <legend>List B</legend> <input id="input-B1" type="radio" name="input" aria-controls="graphic" data-controls="item-B1 item-A2 item-A3 item-A4"/> <label for="input-B1">B1</label> <input id="input-B2" type="radio" name="input" aria-controls="graphic" data-controls="item-B2 item-A2 item-B3"/> <label for="input-B2">B2</label> <input id="input-B3" type="radio" name="input" aria-controls="graphic" data-controls="item-B3 item-A1 item-A4"/> <label for="input-B3">B3</label> <input id="input-B4" type="radio" name="input" aria-controls="graphic" data-controls="item-B4 item-A2"/> <label for="input-B4">B4</label> </fieldset> </form> </div> <h2 class="sr-only">Visualization</h2> <figure id="graphic" aria-live="polite"> <ul aria-label="List A"> <li class="item" id="item-A1" data-state="inactive">A1<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-A2" data-state="inactive">A2<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-A3" data-state="inactive">A3<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-A4" data-state="inactive">A4<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> </ul> <ul aria-label="List B"> <li class="item" id="item-B1" data-state="inactive">B1<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-B2" data-state="inactive">B2<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-B3" data-state="inactive">B3<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> <li class="item" id="item-B4" data-state="inactive">B4<span class="sr-only selected">, selected</span><span class="sr-only highlighted">, highlighted</span></li> </ul> </figure>
Creo que la solución a su problema es usar el índice de pestañas. Puede leer más sobre esto aquí: https://developers.google.com/web/fundamentals/accessibility/focus/using-tabindex
En resumen, puede usar tabindex="-1" para eliminar un elemento del orden de tabulación natural, pero aún se puede enfocar el elemento llamando a su método focus().
Editar: probablemente me perdí mencionar por qué podría ser necesario configurar el índice de tabulación. Para la accesibilidad, al usar listas interactivas, se debe considerar el hecho de que la interfaz de usuario también debe ser navegable con el teclado. Para esta configuración, ayudará el índice de tabulación para elementos activos e inactivos.
Sin embargo, probablemente debería haber mencionado el uso de aria-live='polite'
( https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions ) para regiones con contenido dinámico. Esto permitirá que el lector de pantalla observe los cambios en esa región y los anuncie. Además, puede usar [role='list'] o [role='group'] para proporcionar contexto adicional a los lectores de pantalla sobre la lista.