Padrão de Botão Icon-Only
Este padrão define quando e como usar botões que contêm apenas ícones, estabelecendo a obrigatoriedade de tooltips descritivos para garantir acessibilidade e usabilidade. O objetivo é manter interfaces limpas sem comprometer a compreensão das ações disponíveis.
Contexto & Problema
Section titled “Contexto & Problema”Botões com apenas ícones podem economizar espaço e criar interfaces mais limpas, mas podem gerar confusão sobre sua função, especialmente para usuários com deficiências visuais ou quando os ícones não são universalmente reconhecidos. A falta de descrição textual pode tornar a interface inacessível.
Quando Usar
Section titled “Quando Usar”- Em barras de ferramentas com espaço limitado
- Para ações secundárias frequentemente utilizadas
- Em interfaces onde o contexto torna a ação óbvia
- Quando há necessidade de manter design minimalista
Componentes Utilizados
Section titled “Componentes Utilizados”- Button (Console Kit Blocks)
- Tooltip (Console Kit Blocks)
- Icon (Console Kit Blocks)
Exemplo Visual
Section titled “Exemplo Visual”Exemplo visual será adicionado em breve
Edge Applications > Lista com botões de ação
Comportamento Esperado
Section titled “Comportamento Esperado”- Obrigatório: Todo botão icon-only deve ter um tooltip descritivo
- Descrição clara: O tooltip deve explicar exatamente qual ação será executada
- Timing adequado: Tooltip deve aparecer rapidamente no hover (300-500ms)
- Posicionamento: Tooltip deve ser posicionado para não obstruir conteúdo importante
- Estado normal: Ícone visível com tooltip no hover
- Estado hover: Tooltip aparece com descrição da ação
- Estado focus: Tooltip visível para navegação por teclado
- Estado disabled: Tooltip explica por que a ação não está disponível
- Estado loading: Indicador de carregamento com tooltip atualizado
Implementação
Section titled “Implementação”const IconOnlyButton = { // Cria botão icon-only com tooltip obrigatório create: (iconName, action, tooltipText, options = {}) => { const button = document.createElement('button'); button.className = `btn-icon-only ${options.variant || 'secondary'}`; button.setAttribute('aria-label', tooltipText); button.setAttribute('data-tooltip', tooltipText);
// Adiciona ícone const icon = document.createElement('i'); icon.className = `icon icon-${iconName}`; button.appendChild(icon);
// Event listeners button.addEventListener('click', action);
// Tooltip handlers button.addEventListener('mouseenter', IconOnlyButton.showTooltip); button.addEventListener('mouseleave', IconOnlyButton.hideTooltip); button.addEventListener('focus', IconOnlyButton.showTooltip); button.addEventListener('blur', IconOnlyButton.hideTooltip);
return button; },
// Gerencia exibição de tooltip showTooltip: (event) => { const button = event.target.closest('[data-tooltip]'); const tooltipText = button.getAttribute('data-tooltip');
// Remove tooltip existente IconOnlyButton.hideTooltip();
// Cria novo tooltip const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.textContent = tooltipText; tooltip.id = 'active-tooltip';
document.body.appendChild(tooltip);
// Posiciona tooltip const buttonRect = button.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect();
tooltip.style.left = `${buttonRect.left + (buttonRect.width - tooltipRect.width) / 2}px`; tooltip.style.top = `${buttonRect.top - tooltipRect.height - 8}px`;
// Adiciona classe para animação setTimeout(() => tooltip.classList.add('visible'), 10); },
hideTooltip: () => { const existingTooltip = document.getElementById('active-tooltip'); if (existingTooltip) { existingTooltip.remove(); } },
// Valida se botão tem tooltip validateTooltip: (button) => { const hasTooltip = button.hasAttribute('data-tooltip') || button.hasAttribute('aria-label') || button.hasAttribute('title');
if (!hasTooltip) { console.warn('Icon-only button missing tooltip:', button); return false; } return true; }};
// Exemplo de usoconst actionButtons = [ { icon: 'edit', action: () => editItem(), tooltip: 'Edit item', variant: 'primary' }, { icon: 'delete', action: () => deleteItem(), tooltip: 'Delete item', variant: 'danger' }, { icon: 'copy', action: () => copyItem(), tooltip: 'Duplicate item' }];
// Renderiza botões na interfaceconst buttonContainer = document.querySelector('.action-buttons');actionButtons.forEach(config => { const button = IconOnlyButton.create( config.icon, config.action, config.tooltip, { variant: config.variant } ); buttonContainer.appendChild(button);});
// Validação de acessibilidadedocument.querySelectorAll('.btn-icon-only').forEach(button => { IconOnlyButton.validateTooltip(button);});Boas Práticas
Section titled “Boas Práticas”Anti-padrões
Section titled “Anti-padrões”Acessibilidade
Section titled “Acessibilidade”- Obrigatório: Use
aria-labeloutitlepara descrever a ação - Implemente suporte completo para navegação por teclado
- Garanta que tooltips sejam anunciados por leitores de tela
- Mantenha contraste adequado entre ícone e fundo
- Use
role="button"quando necessário para elementos não-button - Considere
aria-describedbypara descrições mais detalhadas