Tucano v0.37.2

Teclado e acessibilidade

Tudo é operável sem mouse, e as teclas são as do elemento nativo e do ARIA APG — quem já sabe usar um <select> não precisa aprender nada. Esta página junta num lugar só o que cada componente faz com o teclado e o que ele anuncia a quem usa leitor de tela.

Tab não abre painel

Chegar num campo pelo Tab nunca abre calendário, lista ou seletor de cor. Abrir é sempre explícito: ↓, clique, e no select também Enter e Espaço.

Abrir no foco parecia conveniente e era a origem de dois defeitos. Os painéis se empilhavam ao tabular — quem atravessa um formulário até o botão de salvar levava um calendário na cara a cada campo, com o painel cobrindo o campo seguinte. E no campo de cor o painel cobria o próprio campo de quem queria digitar o hex. É a regra do <select> nativo, e é a do ARIA APG.

ComponenteAbre com
Date picker↓, clique; Espaço com o campo vazio
Select↓, ↑, Enter, Espaço, clique
Color picker↓ no campo ou na amostra; Enter e Espaço na amostra, que é um <button>
Menu suspenso↓, ↑, Enter, Espaço ou clique no gatilho

No campo de data, o Enter é do formulário

O date picker é um campo de texto dentro de um <form>, e Enter num campo de texto envia o formulário — é o que qualquer pessoa espera depois de digitar a data. Sequestrar a tecla quebraria o envio em silêncio em todo formulário que já existe, então ali quem abre é o Espaço. E só com o campo vazio: em modo com hora se digita 07/09/2026 14:30, e o espaço é digitação. Com o painel aberto, o Enter confirma o que foi digitado.

Qualquer painel que abriu fecha quando o foco sai dele, então tabular pela página nunca deixa menus abertos para trás.

Date picker

TeclaOndeAção
↓Campo, fechadoAbre o calendário
EspaçoCampo vazio, fechadoAbre o calendário
EnterCampo, abertoConfirma a data digitada e fecha; fechado, envia o formulário
← →DiaDia anterior e seguinte
↑ ↓DiaMesma data na semana anterior e na seguinte
PageUp PageDownDiaMês anterior e seguinte; com Shift, ano
Home EndDiaInício e fim da semana, respeitando o primeiro dia do locale
Enter EspaçoDiaEscolhe o dia
↑ ↓Coluna de horaValor anterior e seguinte da coluna
Home EndColuna de horaPrimeiro e último valor da coluna
Enter EspaçoColuna de horaEscolhe o valor
TabPainelCircula dentro do painel, sem escapar para a página; cada coluna de hora é uma parada só
EscPainelFecha, descarta o que não foi confirmado e devolve o foco ao campo

O campo é um combobox com aria-haspopup="dialog" e aria-expanded, e ganha aria-controls apontando para o painel enquanto ele está aberto. O painel é role="dialog" com o rótulo "Selecionar data" ou "Selecionar período"; cada mês é um role="grid" com o nome do mês, cada semana um row e cada dia um gridcell com aria-selected e o rótulo por extenso — "sábado, 12 de setembro de 2026". Dia fora dos limites leva aria-disabled: recebe foco, mas não é escolhido. A troca de mês é anunciada por uma região aria-live="polite" que fica fixa no painel. Cada coluna de hora, minuto e segundo é um grupo de botões com uma parada de Tab só, e o valor escolhido leva aria-pressed. Fechar por clique fora não devolve o foco ao campo, para não roubá-lo de onde a pessoa clicou.

Select

TeclaAção
↓ ↑ Enter EspaçoFechado, abre o painel
↑ ↓Aberto, anda entre as opções, pulando as desativadas e dando a volta
Home EndPrimeira e última opção, pulando as desativadas
EnterEscolhe a opção ativa; no múltiplo, marca ou desmarca
LetrasCom busca ligada, filtram a lista ignorando acentos. A busca liga sozinha a partir de 6 opções, ou com data-search="true"
BackspaceNo múltiplo, com a busca vazia, remove a última tag
Backspace DeleteNo simples, com a busca vazia, limpa o valor, como o X — sem o X (clearable: false), não limpa
EscFecha e devolve o foco ao campo

Com o <select> nativo disabled, o campo sai do caminho do Tab, fica esmaecido, não abre, não aceita texto e esconde o X de limpar e o das tags. Opção dentro de <optgroup disabled> também conta como desativada.

O controle é um combobox com aria-haspopup="listbox", aria-expanded e aria-controls apontando para a lista enquanto ela está aberta — fechada, a lista sai do DOM e o atributo sai junto. A lista é listbox, com aria-multiselectable no múltiplo; cada opção é option com aria-selected e aria-disabled. A opção ativa chega ao leitor de tela por aria-activedescendant, sem tirar o foco do campo de busca. O X de cada tag se chama "Remover" seguido do nome da opção. O <select> nativo continua no formulário com aria-hidden, para não ser lido duas vezes.

Color picker

TeclaOndeAção
↓Campo ou amostraAbre o painel com o foco na área de cor
Enter EspaçoAmostraAbre o painel com o foco na área de cor, e fecha
← →Área de corSaturação, de 2 em 2%; com Shift, de 10 em 10
↑ ↓Área de corBrilho, no mesmo passo
← ↓ / → ↑Barras de matiz e opacidadeDiminui e aumenta de 1 em 1; com Shift, de 10 em 10
Home EndBarras de matiz e opacidadeMínimo e máximo
EscPainelFecha e devolve o foco à amostra

A amostra é um <button> "Escolher cor" com aria-haspopup="dialog" e aria-expanded; o painel é role="dialog" "Seletor de cor". As barras são role="slider" com aria-valuenow, e cada cor da paleta é um botão com o próprio valor como rótulo. O campo de texto continua sendo o <input> onde se digita o hex.

TeclaOndeAção
↓GatilhoAbre e entra no primeiro item
↑GatilhoAbre e entra no último item
Enter EspaçoGatilhoAbre e fecha
↑ ↓MenuItem anterior e seguinte, dando a volta e pulando os desativados
Home EndMenuPrimeiro e último item
EnterItemAciona; item sem href é <button> e aceita também Espaço
EscMenuFecha e devolve o foco ao gatilho
TabMenuFecha e segue para o próximo elemento da página

O gatilho ganha aria-haspopup="menu" e aria-expanded; o painel é role="menu", cada item menuitem, o separador separator e o item desativado aria-disabled. Os itens têm tabindex="-1" de propósito: dentro de um menu quem navega é a seta, e itens tabuláveis fariam o Tab sair do menu item a item.

Abas

TeclaAção
TabEntra na aba aberta e sai da lista direto para o painel — a lista é uma parada só
← →Aba anterior e seguinte, dando a volta e pulando as desativadas
Home EndPrimeira e última aba
Enter EspaçoCom data-manual="true", troca o painel; sem ele, a seta já troca

O script põe tablist, tab e tabpanel, liga cada aba ao painel por aria-controls e aria-labelledby, e mantém aria-selected e o tabindex em dia. O modo manual existe para painel que carrega por HTMX: com ativação automática, atravessar quatro abas com a seta dispararia quatro requisições.

Modal e gaveta

TeclaAção
Tab Shift+TabCirculam dentro do diálogo, sem escapar para a página
EscFecha com animação e devolve o foco a quem abriu — a não ser com closable: false ou data-closable="false"

Os dois são <dialog> aberto com showModal(), e é daí que vem a acessibilidade: a página atrás fica inerte, o foco fica preso e volta ao gatilho no fechamento, tudo pelo navegador e não por JavaScript nosso. O Escape nativo fecharia na hora, sem animação; a biblioteca intercepta o cancel para fechar pelo mesmo caminho do X. Com título, o diálogo é rotulado por ele via aria-labelledby, e o X se chama "Fechar". No Tucano.confirm(), fechar pelo Esc é uma recusa: a promessa resolve false.

Tooltip

Tecla ou gestoAção
Tab até o elementoMostra a dica na hora, sem o atraso do ponteiro
Tab para foraEsconde a dica
EscEsconde, mesmo com o ponteiro parado em cima
ToqueEm tela de toque, tocar abre e tocar de novo fecha

O balão é role="tooltip" e o elemento aponta para ele por aria-describedby, então o leitor de tela lê a dica junto com o nome do botão. Um elemento que não recebe foco — um <span>, por exemplo — ganha tabindex="0", senão quem usa teclado nunca veria a dica. O Esc com o ponteiro em cima é o que a WCAG 1.4.13 pede, para quem usa ampliação de tela e precisa tirar da frente uma dica que cobre o conteúdo.

Tabela

TeclaAção
TabPassa pelos cabeçalhos ordenáveis, pelas caixas de seleção e pelos botões das linhas
EnterNo cabeçalho, ordena — no modo servidor, segue o link
EspaçoMarca a caixa; no modo cliente, também ordena pelo botão do cabeçalho

O cabeçalho ordenado tem aria-sort="ascending" ou "descending" e os outros "none". No modo servidor o gatilho é um <a>, anunciado como link; no cliente, um <button>. A caixa do cabeçalho é "Selecionar todas as linhas desta página", a de cada linha "Selecionar linha", e o estado misto é o indeterminate nativo.

Os outros componentes

Estes não têm teclado próprio: o que responde é o elemento nativo por baixo. Mesmo assim, cada um tem algo a dizer ao leitor de tela.

ComponenteTecladoO que anuncia
AcordeãoEnter e Espaço no <summary>, do navegadorAberto e fechado, pelo <details>; a seta é aria-hidden
UploadEnter e Espaço na zona abrem a janela de arquivosA zona é role="button" descrita pela dica de tipos e tamanho; arquivo recusado aparece num aviso role="alert"
Máscara com olhoEnter e Espaço no botão do olho"Mostrar" e "Ocultar", com aria-pressed; com data-validate, o campo reprovado ganha aria-invalid
Editor de textoCtrl/⌘ + B, I, U, K; Tab numa tabela anda de célula e, na última, cria uma linhaA área é role="textbox" multilinha; as barras são role="toolbar" e os botões de formatação têm aria-pressed
ToastO relógio pausa quando algo dentro dele recebe focoDuas regiões ao vivo criadas antes da primeira mensagem: polite para o normal e assertive para erro; cada toast é status ou alert
PaginaçãoLinks nativos<nav> "Paginação", aria-current="page" na atual; a ponta desativada é <span>, fora do caminho do Tab
Caixa, opção e chaveAs do <input> nativoAs do navegador; a chave precisa de role="switch" no template, porque CSS não põe papel
Grupo de botõesAs do <input> nativo: o grupo de rádios é uma parada só do Tab, e as setas andam por elePrecisa de nome no grupo — role="group" com aria-label, ou <fieldset> com <legend>; o <input> fica invisível, mas focável e anunciado

O que vale para todos

As decisões que se repetem de componente em componente.

RegraPor quê
O elemento nativo continua no formulárioNome, obrigatoriedade, estado misto e o anúncio do leitor de tela continuam sendo os do navegador
Campo com erro é aria-invalid="true", e não classeÉ o atributo que o leitor de tela anuncia, e o Django 5 o escreve sozinho. Nos componentes que trocam o campo por um controle próprio, o atributo fica no nativo e o CSS alcança o controle
Painel que abriu fecha quando o foco saiSem isso, tabular pela página deixava um rastro de painéis abertos
Esc fecha e devolve o foco a quem abriuFechar largando o foco no começo da página faria quem usa teclado recomeçar do topo
Desenho vai com aria-hiddenSetas, ícones e reticências da paginação não são lidos em voz alta
prefers-reduced-motion desliga as animaçõesVale para painéis, diálogos, toasts, abas, acordeão, tabela e botões