Dicas de acordo com a posição

Ao criar dicas ou popovers com o posicionamento de âncora do CSS, o navegador pode "virar" automaticamente o elemento para uma posição substituta se ele transbordar a janela de visualização. Quando isso acontece, talvez seja necessário ajustar o estilo do conteúdo posicionado, por exemplo, para reposicionar uma seta que aponta do conteúdo posicionado para a âncora.

As consultas de contêiner ancoradas resolvem isso permitindo que você consulte o estado de posicionamento ativo de um elemento e aplique estilos de acordo. Para posicionamento geral de menus suspensos e de sobreposição fixos com inversão de colisão de janela de visualização (sem reestilização de seta direcional), consulte Menus suspensos que consideram a janela de visualização.

O problema

Imagine uma dica que aparece acima da âncora por padrão. Ele tem uma seta para baixo na parte inferior. Se o usuário rolar a tela e a dica aparecer abaixo da âncora, a seta vai apontar para o lado errado e ficar no lado errado da dica.

A solução: consultas de contêiner ancoradas

Ao definir container-type: anchored no elemento posicionado, você o transforma em um contêiner de consulta que conhece o próprio estado posicionado por âncora. Em seguida, use a consulta @container anchored() para atualizar os descendentes ou pseudoelementos.

1. Criar a dica e o gatilho

Use a API Popover para criar uma dica. Invocar o popover usando popovertarget estabelece automaticamente uma conexão de âncora implícita com o botão de acionamento, então não é necessário usar anchor-name ou position-anchor de forma explícita.

<button popovertarget="tooltip" id="anchor" aria-describedby="tooltip">anchor</button>
<div id="tooltip" popover role="tooltip"></div>

Redefina os estilos de margem e inserção do popover para uso com o posicionamento de âncora, mas somente se o posicionamento de âncora for compatível.

@supports (anchor-name: --my-anchor) {
  [popover] {
    inset: auto;
    margin: unset;
  }
}

2. Configurar o contêiner

Aplique container-type: anchored ao elemento que está sendo posicionado. Esse elemento também precisa ter position-try-fallbacks definido para ativar o comportamento de inversão.

#tooltip {
  position: fixed;
  position-area: block-start;
  position-try-fallbacks: flip-block;

  /* Enable anchored container queries */
  container-type: anchored;
}

3. Estilo com base no substituto

Use @container anchored(fallback: <value>) para aplicar estilos quando um substituto específico estiver ativo.

Como todas as consultas de contêiner, @container só pode definir o estilo dos descendentes do contêiner. Uma estratégia comum para criar as setas é usar os pseudoelementos ::before e ::after, que são tratados como descendentes e podem ser estilizados diretamente. No entanto, para estilizar a dica em si (como visto na etapa 4), vamos adicionar um elemento filho à dica e criar a seta no pseudoelemento ::before.

<div id="tooltip" popover role="tooltip">
  <div class="tooltip-content">Tooltip</div>
</div>
.tooltip-content::before {
  /* Default "down" arrow for the 'top' position */
  content: "▼";
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 1rem;
}

/* Update to an "up" arrow when the 'flip-block' fallback (bottom) is active */
@container anchored(fallback: flip-block) {
  .tooltip-content::before {
    content: "▲";
    inset-block-start: 0;
    inset-block-end: auto;
  }
}

4. Estilizar o próprio contêiner

Se você precisar mudar as propriedades do próprio contêiner (como margin ou background-color) quando ele for invertido, use um elemento wrapper interno.

  1. Aplique container-type: anchored ao elemento posicionado externamente.
  2. Segmente o elemento interno dentro do bloco @container.
@container anchored(fallback: flip-block) {
  .tooltip-content {
    border-radius: 0 0 .5rem .5rem;
    margin-block-start: 0.25rem;
  }
}

Práticas recomendadas

  • Prefira substitutos lógicos: use palavras-chave como flip-block e flip-inline em position-try-fallbacks para consultas mais simples que processam RTL e diferentes modos de escrita automaticamente.
  • Use pseudoelementos para setas: as setas de dicas são puramente decorativas e são candidatas perfeitas para ::before ou ::after, que podem ser estilizadas com consultas de contêiner ancoradas sem DOM extra.

Estratégias de fallback

Posicionar a seta com base no substituto aplicado é um aprimoramento progressivo, e não há outra maneira de reagir à posição de substituição. Para ocultar a seta em navegadores que não são compatíveis com consultas de contêiner de posição de âncora, teste a compatibilidade com CSS usando @supports (container-type: anchored).

@supports (container-type: anchored) {
  .tooltip-content::before {
    content: "▼";
  }
}

Alternativas e suporte do navegador para Popover

A API Popover é, em grande parte, um aprimoramento progressivo, mas os comportamentos que a definem (promoção de camada superior, fechamento leve e invocação de popovertarget) não têm equivalente somente em CSS. Os navegadores mais antigos precisam de um polyfill ou de um substituto manual, se você preferir não enviar um.

Polyfill: para oferecer suporte ao atributo popover em navegadores mais antigos, carregue condicionalmente @oddbird/popover-polyfill. OBRIGATÓRIO: detecte o recurso verificando a propriedade popover em HTMLElement.prototype e carregue o polyfill somente quando não houver suporte nativo. NÃO o carregue incondicionalmente.

Com um bundler ou mapa de importação:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Sem um bundler, importe de uma CDN dentro de um <script type="module">:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Observação sobre o estilo: o polyfill não pode definir a pseudoclasse :popover-open real. Por isso, ele aplica uma classe .\:popover-open. OBRIGATÓRIO: combine os dois com :is() ou :where(). Caso contrário, os navegadores que não têm :popover-open vão descartar toda a regra:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

Como alternativa, para um substituto legado sem um polyfill, use position: fixed e calcule manualmente as coordenadas usando getBoundingClientRect() ou use o posicionamento padrão com inset: auto, se isso for aceitável para o caso de uso.

Os navegadores sem suporte à API Popover também não oferecem suporte ao posicionamento de âncora. Portanto, a dica vai aparecer no centro da tela.