CSS anchor positioning: o fim das gambiarras de tooltip e popover

1. O Problema Histórico: Posicionamento de Elementos Flutuantes

Por anos, desenvolvedores web enfrentaram um pesadelo recorrente: posicionar tooltips, popovers e menus dropdown de forma precisa e responsiva. A abordagem clássica dependia de JavaScript puro — ou bibliotecas como Popper.js — para calcular coordenadas com getBoundingClientRect(), monitorar eventos de scroll e redimensionamento, e ajustar manualmente as posições.

As gambiarras eram muitas: position: absolute com margens negativas, combinações de top e left calculados dinamicamente, e hacks com transform: translate() para evitar reflows. Em layouts responsivos, o cenário piorava: ao zoomar ou redimensionar a janela, tooltips quebravam, sobrepunham conteúdo ou simplesmente desapareciam.

O problema central era que CSS não oferecia uma forma declarativa de dizer: "posicione este elemento em relação àquele outro". Tudo precisava ser resolvido em tempo de execução com JavaScript, gerando complexidade, bugs de layout shift (CLS) e manutenção dolorosa.

2. Introdução ao CSS Anchor Positioning API

O CSS Anchor Positioning API resolve esse problema de forma nativa. A ideia é simples: qualquer elemento pode ser uma âncora (anchor) e outros elementos podem se posicionar em relação a ela como alvos (targets).

As propriedades fundamentais são:

  • anchor-name: define um identificador para o elemento âncora (ex: --meu-botao)
  • position-anchor: no elemento alvo, referencia a âncora pelo nome
  • anchor(): função que calcula posições relativas à âncora
  • inset-area: posiciona o alvo em áreas predefinidas ao redor da âncora

O suporte atual inclui Chrome 125+, Edge 125+, e Safari Technology Preview. Firefox está em desenvolvimento. Para produção, é recomendado usar como progressive enhancement.

3. Sintaxe Básica: Vinculando um Popover a um Botão

Vamos ao exemplo mais simples: um botão que abre um popover posicionado acima dele.

<button id="meu-botao" popovertarget="meu-popover">Abrir</button>
<div id="meu-popover" popover>
  Conteúdo do popover
</div>

<style>
  #meu-botao {
    anchor-name: --meu-botao;
  }

  #meu-popover {
    position-anchor: --meu-botao;
    inset-area: top;
  }
</style>

Com apenas três linhas de CSS, o popover se posiciona automaticamente acima do botão. Quando o botão é clicado, o popover abre sem uma linha de JavaScript. O navegador gerencia scroll, redimensionamento e posicionamento.

4. Controle Fino de Posicionamento com inset-area

A propriedade inset-area aceita combinações poderosas:

/* Posicionamento básico */
inset-area: top;           /* acima, centralizado */
inset-area: bottom;        /* abaixo, centralizado */
inset-area: left;          /* à esquerda, centralizado */
inset-area: right;         /* à direita, centralizado */

/* Combinações com duas palavras */
inset-area: top left;      /* canto superior esquerdo */
inset-area: bottom right;  /* canto inferior direito */

/* Centralizado em relação à âncora */
inset-area: center;        /* exatamente sobre a âncora */

Para ajustes finos, use a função anchor():

#meu-popover {
  position-anchor: --meu-botao;
  top: anchor(--meu-botao bottom + 8px);
  left: anchor(--meu-botao left);
}

Para prevenir overflow em telas pequenas, use position-try-fallbacks:

#meu-popover {
  position-anchor: --meu-botao;
  inset-area: top;
  position-try-fallbacks: bottom, left, right;
}

Isso instrui o navegador: "tente top primeiro; se não couber, tente bottom; depois left; depois right".

5. Casos de Uso Práticos: Tooltips e Popovers Sem JavaScript

Tooltip em hover com CSS puro

<button class="tooltip-trigger" aria-describedby="tooltip1">
  Salvar
  <span class="tooltip" id="tooltip1">Salva as alterações atuais</span>
</button>

<style>
  .tooltip-trigger {
    anchor-name: --trigger;
    position: relative;
  }

  .tooltip {
    position-anchor: --trigger;
    inset-area: bottom;
    display: none;
    background: #333;
    color: #fff;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    white-space: nowrap;
  }

  .tooltip-trigger:hover .tooltip,
  .tooltip-trigger:focus .tooltip {
    display: block;
  }
</style>

Popover de menu dropdown

<button popovertarget="menu-dropdown" style="anchor-name: --menu-btn">
  Opções ▼
</button>
<nav id="menu-dropdown" popover style="position-anchor: --menu-btn; inset-area: bottom">
  <ul>
    <li><a href="#">Editar</a></li>
    <li><a href="#">Duplicar</a></li>
    <li><a href="#">Excluir</a></li>
  </ul>
</nav>
<button popovertarget="confirm-modal" style="anchor-name: --excluir-btn">
  Excluir item
</button>
<div id="confirm-modal" popover style="position-anchor: --excluir-btn; inset-area: center">
  <p>Tem certeza que deseja excluir?</p>
  <button popovertarget="confirm-modal" popovertargetaction="hide">Cancelar</button>
  <button>Confirmar</button>
</div>

6. Resolvendo Problemas Comuns com Fallbacks

Evitando layout shift (CLS)

Diferente de soluções JavaScript que causam reflow ao reposicionar elementos, o Anchor Positioning calcula a posição antes do paint. O elemento já nasce na posição correta, sem saltos visuais.

Comportamento em telas pequenas

#popover {
  position-anchor: --ancora;
  inset-area: right;
  position-try-options: flip-block, flip-inline;
}

flip-block inverte de cima para baixo; flip-inline inverte de esquerda para direita. Isso garante que o popover sempre fique visível.

Fallback para navegadores sem suporte

#popover {
  /* Fallback: posicionamento absoluto tradicional */
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);

  /* Anchor Positioning para navegadores compatíveis */
  position-anchor: --ancora;
  inset-area: bottom;
}

Navegadores modernos ignoram o fallback e usam o anchor; navegadores antigos usam o posicionamento absoluto.

7. Comparação com Abordagens Antigas

Característica JavaScript (Popper.js) Gambiarra CSS Anchor Positioning
Código necessário 50+ linhas JS + CSS 20+ linhas CSS + hacks 3-5 linhas CSS
Performance Reflow constante, listeners Reflow em zoom/scroll Zero reflow forçado
Responsividade Precisa de resize observer Quebra facilmente Nativa e automática
Acessibilidade Depende de implementação ARIA manual + foco manual Nativa com popover
Manutenção Alta (atualizar libs) Média (hacks quebram) Baixa (especificação estável)

8. Exemplo Completo: Sistema de Tooltips Temáticos

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tooltips com Anchor Positioning</title>
  <style>
    :root {
      --tooltip-bg: #333;
      --tooltip-color: #fff;
      --tooltip-delay: 0.3s;
    }

    [data-theme="light"] {
      --tooltip-bg: #f0f0f0;
      --tooltip-color: #333;
    }

    .btn {
      anchor-name: --btn;
      padding: 8px 16px;
      border: 1px solid #ccc;
      border-radius: 4px;
      cursor: pointer;
      position: relative;
    }

    .tooltip {
      position-anchor: --btn;
      inset-area: top;
      background: var(--tooltip-bg);
      color: var(--tooltip-color);
      padding: 6px 12px;
      border-radius: 6px;
      font-size: 13px;
      white-space: nowrap;
      pointer-events: none;
      opacity: 0;
      transition: opacity var(--tooltip-delay) ease;
    }

    .btn:hover .tooltip,
    .btn:focus-visible .tooltip {
      opacity: 1;
    }

    .tooltip::after {
      content: '';
      position: absolute;
      top: 100%;
      left: 50%;
      transform: translateX(-50%);
      border: 6px solid transparent;
      border-top-color: var(--tooltip-bg);
    }
  </style>
</head>
<body>
  <button class="btn">
    Salvar
    <span class="tooltip" role="tooltip">Salva as alterações atuais</span>
  </button>

  <button class="btn">
    Excluir
    <span class="tooltip" role="tooltip">Remove permanentemente</span>
  </button>

  <div data-theme="light">
    <button class="btn">
      Modo claro
      <span class="tooltip" role="tooltip">Tema alternativo</span>
    </button>
  </div>
</body>
</html>

Este exemplo demonstra:
- Tooltips sem JavaScript, apenas HTML + CSS
- Suporte a temas claro/escuro com CSS variables
- Transição suave com delay
- Seta indicadora estilizada com pseudo-elemento
- Acessibilidade com role="tooltip" e foco via teclado

O resultado é um sistema de tooltips performático, acessível e fácil de manter — exatamente o que o CSS Anchor Positioning promete entregar.

Referências