Teclado y accesibilidad
Todo se puede usar sin ratón, y las teclas son las del elemento nativo y las del ARIA APG — quien ya sabe usar un
<select> no tiene que aprender nada. Esta página reúne en un solo lugar lo que cada componente
hace con el teclado y lo que anuncia a quien usa lector de pantalla. Las etiquetas accesibles que la biblioteca
escribe por su cuenta están en portugués por defecto, y Tucano.setTexts() las reemplaza; abajo aparecen entre comillas con su significado.
Tab no abre paneles
Llegar a un campo con Tab nunca abre calendario, lista ni selector de color. Abrir siempre es
explícito: ↓, clic, y en el select también Enter y Espacio.
Abrir al recibir el foco parecía cómodo y era el origen de dos defectos. Los paneles se amontonaban al tabular — quien
recorría un formulario hasta el botón de guardar se encontraba un calendario en la cara en cada campo, con el panel tapando
el campo siguiente. Y en el campo de color el panel tapaba el propio campo de quien quería escribir el hex. Es la regla del
<select> nativo, y es la del ARIA APG.
| Componente | Se abre con |
|---|---|
| Date picker | ↓, clic; Espacio con el campo vacío |
| Select | ↓, ↑, Enter, Espacio, clic |
| Color picker | ↓ en el campo o en la muestra; Enter y Espacio en la muestra, que es un <button> |
| Menú desplegable | ↓, ↑, Enter, Espacio o clic en el disparador |
En el campo de fecha, el Enter es del formulario
El date picker es un campo de texto dentro de un <form>, y Enter en un campo de texto envía el formulario — es lo que cualquiera espera después de escribir la fecha. Secuestrar la tecla rompería el envío en silencio en todos los formularios existentes, así que ahí quien abre es el Espacio. Y solo con el campo vacío: en modo con hora se escribe 07/09/2026 14:30, y el espacio forma parte de lo escrito. Con el panel abierto, el Enter confirma lo que se escribió.
Cualquier panel que se abrió se cierra cuando el foco sale de él, así que tabular por la página nunca deja menús abiertos atrás.
Date picker
| Tecla | Dónde | Acción |
|---|---|---|
↓ | Campo, cerrado | Abre el calendario |
Espacio | Campo vacío, cerrado | Abre el calendario |
Enter | Campo, abierto | Confirma la fecha escrita y cierra; cerrado, envía el formulario |
← → | Día | Día anterior y siguiente |
↑ ↓ | Día | Misma fecha en la semana anterior y en la siguiente |
PageUp PageDown | Día | Mes anterior y siguiente; con Shift, año |
Home End | Día | Inicio y fin de la semana, respetando el primer día del locale |
Enter Espacio | Día | Elige el día |
↑ ↓ | Columna de hora | Valor anterior y siguiente de la columna |
Home End | Columna de hora | Primer y último valor de la columna |
Enter Espacio | Columna de hora | Elige el valor |
Tab | Panel | Circula dentro del panel, sin escaparse a la página; cada columna de hora es una sola parada |
Esc | Panel | Cierra, descarta lo que no se confirmó y devuelve el foco al campo |
El campo es un combobox con aria-haspopup="dialog" y aria-expanded, y recibe
aria-controls apuntando al panel mientras está abierto. El panel es role="dialog"
con la etiqueta "Selecionar data" (seleccionar fecha) o "Selecionar período" (seleccionar período); cada mes es un role="grid" con el nombre del mes,
cada semana un row y cada día un gridcell con aria-selected y la etiqueta completa — "sábado, 12 de
septiembre de 2026". Un día fuera de los límites lleva aria-disabled: recibe el foco, pero no se elige. El cambio de mes
se anuncia en una región aria-live="polite" que queda fija en el panel. Cada columna de hora, minuto y segundo es un
grupo de botones con una sola parada de Tab, y el valor elegido lleva aria-pressed. Cerrar con un clic fuera no devuelve el foco al campo, para no
quitárselo al lugar donde la persona hizo clic.
Select
| Tecla | Acción |
|---|---|
↓ ↑ Enter Espacio | Cerrado, abre el panel |
↑ ↓ | Abierto, recorre las opciones, saltando las desactivadas y dando la vuelta |
Home End | Primera y última opción, saltando las desactivadas |
Enter | Elige la opción activa; en el múltiple, la marca o la desmarca |
| Letras | Con la búsqueda activada, filtran la lista ignorando tildes. La búsqueda se activa sola a partir de 6 opciones, o con data-search="true" |
Backspace | En el múltiple, con la búsqueda vacía, quita la última etiqueta |
Backspace Delete | En el simple, con la búsqueda vacía, limpia el valor, como la X — sin la X (clearable: false), no limpia |
Esc | Cierra y devuelve el foco al campo |
Con el <select> nativo disabled, el campo sale del recorrido del Tab, queda
atenuado, no abre, no acepta texto y oculta la X de limpiar y la de las etiquetas. Una opción dentro de
<optgroup disabled> también cuenta como desactivada.
El control es un combobox con aria-haspopup="listbox", aria-expanded y
aria-controls apuntando a la lista mientras está abierta — cerrada, la lista sale del DOM y el atributo
se va con ella. La lista es listbox, con
aria-multiselectable en el múltiple; cada opción es option con aria-selected y
aria-disabled. La opción activa llega al lector de pantalla por aria-activedescendant, sin quitar
el foco del campo de búsqueda. La X de cada etiqueta se llama "Remover" (quitar) seguido del nombre de la opción. El
<select> nativo sigue en el formulario con aria-hidden, para que no se lea dos
veces.
Color picker
| Tecla | Dónde | Acción |
|---|---|---|
↓ | Campo o muestra | Abre el panel con el foco en el área de color |
Enter Espacio | Muestra | Abre el panel con el foco en el área de color, y lo cierra |
← → | Área de color | Saturación, de 2 en 2%; con Shift, de 10 en 10 |
↑ ↓ | Área de color | Brillo, con el mismo paso |
← ↓ / → ↑ | Barras de tono y opacidad | Disminuye y aumenta de 1 en 1; con Shift, de 10 en 10 |
Home End | Barras de tono y opacidad | Mínimo y máximo |
Esc | Panel | Cierra y devuelve el foco a la muestra |
La muestra es un <button> "Escolher cor" (elegir color) con aria-haspopup="dialog" y
aria-expanded; el panel es role="dialog" "Seletor de cor" (selector de color). Las barras son
role="slider" con aria-valuenow, y cada color de la paleta es un botón con su propio valor como
etiqueta. El campo de texto sigue siendo el <input> donde se escribe el hex.
Menú desplegable
| Tecla | Dónde | Acción |
|---|---|---|
↓ | Disparador | Abre y entra en el primer elemento |
↑ | Disparador | Abre y entra en el último elemento |
Enter Espacio | Disparador | Abre y cierra |
↑ ↓ | Menú | Elemento anterior y siguiente, dando la vuelta y saltando los desactivados |
Home End | Menú | Primer y último elemento |
Enter | Elemento | Lo acciona; un elemento sin href es <button> y acepta también Espacio |
Esc | Menú | Cierra y devuelve el foco al disparador |
Tab | Menú | Cierra y sigue al siguiente elemento de la página |
El disparador recibe aria-haspopup="menu" y aria-expanded; el panel es role="menu", cada
elemento menuitem, el separador separator y el elemento desactivado aria-disabled. Los elementos
tienen tabindex="-1" a propósito: dentro de un menú quien navega es la flecha, y unos elementos tabulables harían que el
Tab saliera del menú elemento a elemento.
Pestañas
| Tecla | Acción |
|---|---|
Tab | Entra en la pestaña abierta y sale de la lista directo al panel — la lista es una sola parada |
← → | Pestaña anterior y siguiente, dando la vuelta y saltando las desactivadas |
Home End | Primera y última pestaña |
Enter Espacio | Con data-manual="true", cambia el panel; sin él, la flecha ya lo cambia |
El script pone tablist, tab y tabpanel, une cada pestaña a su panel con
aria-controls y aria-labelledby, y mantiene al día aria-selected y el
tabindex. El modo manual existe para paneles que cargan por HTMX: con activación automática,
recorrer cuatro pestañas con la flecha dispararía cuatro peticiones.
Modal y panel lateral
| Tecla | Acción |
|---|---|
Tab Shift+Tab | Circulan dentro del diálogo, sin escaparse a la página |
Esc | Cierra con animación y devuelve el foco a quien lo abrió — salvo con closable: false o data-closable="false" |
Los dos son un <dialog> abierto con showModal(), y de ahí viene la accesibilidad: la
página de detrás queda inerte, el foco queda atrapado y vuelve al disparador al cerrar, todo por el navegador y no por
JavaScript nuestro. El Escape nativo cerraría al instante, sin animación; la biblioteca intercepta el
cancel para cerrar por el mismo camino que la X. Con título, el diálogo queda etiquetado por él vía
aria-labelledby, y la X se llama "Fechar" (cerrar). En Tucano.confirm(), cerrar con
Esc es un rechazo: la promesa se resuelve con false.
Tooltip
| Tecla o gesto | Acción |
|---|---|
Tab hasta el elemento | Muestra la pista al instante, sin el retraso del puntero |
Tab hacia fuera | Oculta la pista |
Esc | La oculta, incluso con el puntero quieto encima |
| Toque | En pantalla táctil, tocar abre y volver a tocar cierra |
El globo es role="tooltip" y el elemento apunta a él con aria-describedby, así que el lector
de pantalla lee la pista junto con el nombre del botón. Un elemento que no recibe foco — un <span>, por
ejemplo — recibe tabindex="0"; si no, quien usa teclado nunca vería la pista. El Esc con el
puntero encima es lo que pide la WCAG 1.4.13, para quien usa ampliación de pantalla y necesita apartar una pista
que tapa el contenido.
Tabla
| Tecla | Acción |
|---|---|
Tab | Pasa por las cabeceras ordenables, por las casillas de selección y por los botones de las filas |
Enter | En la cabecera, ordena — en el modo servidor, sigue el enlace |
Espacio | Marca la casilla; en el modo cliente, también ordena con el botón de la cabecera |
La cabecera ordenada tiene aria-sort="ascending" o "descending" y las demás
"none". En el modo servidor el disparador es un <a>, anunciado como enlace; en el cliente, un
<button>. La casilla de la cabecera es "Selecionar todas as linhas desta página" (seleccionar todas las filas de esta página), la de cada fila
"Selecionar linha" (seleccionar fila), y el estado mixto es el indeterminate nativo.
Los demás componentes
Estos no tienen teclado propio: lo que responde es el elemento nativo de debajo. Aun así, cada uno tiene algo que decirle al lector de pantalla.
| Componente | Teclado | Lo que anuncia |
|---|---|---|
| Acordeón | Enter y Espacio en el <summary>, del navegador | Abierto y cerrado, por el <details>; la flecha es aria-hidden |
| Upload | Enter y Espacio en la zona abren la ventana de archivos | La zona es role="button" descrita por la pista de tipos y tamaño; un archivo rechazado aparece en un aviso role="alert" |
| Máscara con ojo | Enter y Espacio en el botón del ojo | "Mostrar" y "Ocultar", con aria-pressed; con data-validate, el campo que no pasa recibe aria-invalid |
| Editor de texto | Ctrl/⌘ + B, I, U, K; Tab en una tabla avanza de celda y, en la última, crea una fila | El área es role="textbox" multilínea; las barras son role="toolbar" y los botones de formato tienen aria-pressed |
| Toast | El temporizador se pausa cuando algo dentro de él recibe el foco | Dos regiones en vivo creadas antes del primer mensaje: polite para lo normal y assertive para errores; cada toast es status o alert |
| Paginación | Enlaces nativos | <nav> "Paginação" (paginación), aria-current="page" en la actual; el extremo desactivado es un <span>, fuera del camino del Tab |
| Casilla, opción e interruptor | Las del <input> nativo | Las del navegador; el interruptor necesita role="switch" en la plantilla, porque el CSS no pone roles |
| Grupo de botones | Las del <input> nativo: el grupo de radios es una sola parada del Tab, y las flechas lo recorren | El grupo necesita nombre — role="group" con aria-label, o <fieldset> con <legend>; el <input> queda invisible, pero enfocable y anunciado |
Lo que vale para todos
Las decisiones que se repiten de componente en componente.
| Regla | Por qué |
|---|---|
| El elemento nativo sigue en el formulario | Nombre, obligatoriedad, estado mixto y el anuncio del lector de pantalla siguen siendo los del navegador |
Un campo con error es aria-invalid="true", y no una clase | Es el atributo que anuncia el lector de pantalla, y Django 5 lo escribe solo. En los componentes que cambian el campo por un control propio, el atributo se queda en el nativo y el CSS alcanza el control |
| El panel que se abrió se cierra cuando sale el foco | Sin esto, tabular por la página dejaba un rastro de paneles abiertos |
Esc cierra y devuelve el foco a quien abrió | Cerrar soltando el foco al principio de la página obligaría a quien usa teclado a empezar desde arriba |
Los dibujos van con aria-hidden | Flechas, iconos y puntos suspensivos de la paginación no se leen en voz alta |
prefers-reduced-motion desactiva las animaciones | Vale para paneles, diálogos, toasts, pestañas, acordeón, tabla y botones |