Safe area não funciona? Por que env(safe-area-inset-bottom) dá 0

Um diagnóstico de 30 segundos e cada motivo para o inset vir zerado (ou com o valor errado), com a correção e um código para ler os valores no aparelho.

Atualizado em 5 min de leitura

Safe areaiPhone 17 Pro · 402 × 874
iPhone 17 Pro com um app em tela cheia e as cotas das áreas seguras

Diagnóstico em 30 segundos

  1. Falta viewport-fit=cover? Todos os insets ficam em 0. Corrija a meta viewport primeiro.
  2. Testando no computador ou no modo celular do DevTools? Tela retangular não tem safe area: 0 é a resposta certa ali.
  3. Top, left e right dão 0 em retrato, no Safari? É o esperado. Em retrato, a barra e a status bar do próprio Safari cuidam da ilha.
  4. O bottom dá 0 ou pouco no Safari, mas 34 no app instalado? Também é o esperado. No navegador, a barra do Safari fica entre a página e o indicador de início.
  5. O valor está certo e o elemento continua atrás do indicador de início? O inset está no elemento errado, ou o position: fixed está com bottom: 0 sem padding.
  6. Ler pelo JavaScript dá 0? Leia um padding calculado, não uma variável CSS (código no fim).

1. Falta viewport-fit=cover (na maioria das vezes, é isso)

No padrão (viewport-fit=auto), o iOS mantém o layout dentro da área segura e pinta as faixas em volta com a cor de fundo. A página nunca encosta na ilha, então todo env(safe-area-inset-*) vale 0. Para ligar:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

Depois confira se existe uma meta viewport só. Frameworks colocam a deles, e uma segunda meta sem viewport-fit=cover pode ganhar. No console da página:

[...document.querySelectorAll('meta[name=viewport]')].map((m) => m.content);
// tem de voltar uma entrada só, com viewport-fit=cover

Nos frameworks mais comuns:

// Next.js (App Router): app/layout.tsx
import type { Viewport } from 'next';
export const viewport: Viewport = { width: 'device-width', initialScale: 1, viewportFit: 'cover' };
// Nuxt: nuxt.config.ts
export default defineNuxtConfig({
  app: { head: { viewport: 'width=device-width, initial-scale=1, viewport-fit=cover' } },
});

No Vite, no Astro ou em HTML puro, edite a meta no index.html ou no layout. Com o cover ligado, a página passa mesmo por baixo da ilha, e o cabeçalho e a barra de baixo precisam do padding.

2. A barra do Safari está no meio

No modo navegador do iPhone, enquanto a barra do Safari aparece, a página não encosta no fim da tela. Por isso o inset de baixo pode dar 0 ou menos que os 34 px do aparelho, e pode mudar quando a barra abre e recolhe. Fixar 34px no código erra para os dois lados.

O iOS 26 muda um detalhe: no layout Compacto, a barra de abas flutua sobre a página e, com viewport-fit=cover, o conteúdo rola por baixo dela. Elementos fixos no rodapé precisam do inset e de um fundo sólido (o Safari 26 pinta a barra com a cor dos elementos fixos perto da borda de baixo). O guia 100vh no Safari do iPhone explica a barra em detalhe.

Dê sempre um mínimo com max(), para a barra ter respiro quando o inset for 0:

.barra-de-abas {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  padding-bottom: max(env(safe-area-inset-bottom), 12px);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  background: #111;
}
main { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); } /* o conteúdo não fica sob a barra */

3. Retrato e paisagem

Em retrato, os insets que contam são o de cima (status bar e ilha) e o de baixo (indicador de início). Em paisagem a ilha vai para uma lateral: safe-area-inset-left ou -right passa a valer de 50 a 62 px, conforme o modelo, e o de baixo cai para uns 21 px. Layout que só cuida de cima e de baixo fica bom em retrato e põe texto sob a ilha quando o celular gira.

.pagina {
  padding-left: max(env(safe-area-inset-left), 16px);
  padding-right: max(env(safe-area-inset-right), 16px);
}

4. App instalado (standalone)

Aberto pela tela de início, não há barra do Safari: o inset de baixo é a altura inteira do indicador de início, e o de cima depende do estilo da status bar:

  • apple-mobile-web-app-status-bar-style default: a status bar é sólida e a página começa abaixo dela, inset de cima 0.
  • black-translucent: a página passa por baixo da status bar e o safe-area-inset-top vale a altura dela. Use no cabeçalho.

Há um relato antigo no WebKit (bug 236445) em que o modo standalone com viewport-fit=contain dá inset de baixo 0 enquanto o navegador dá um valor. Com cover, você foge dele.

5. No computador e no DevTools, sempre 0

O modo celular do Chrome e do Edge acerta o viewport, a densidade e o user agent, mas não a safe area: todo inset dá 0, em qualquer “iPhone”. O layout pode ficar perfeito ali e continuar embaixo da ilha num celular de verdade.

Para ver os valores reais sem o celular, é preciso uma ferramenta que reproduza as medidas. O Chromium tem um comando no protocolo do DevTools para isso, o Emulation.setSafeAreaInsetsOverride, que uma ferramenta pode chamar com os números de cada aparelho. O Simulador do iOS, no Mac, também informa os valores.

6. Chrome do Android e edge-to-edge

O Chrome do Android entende env(safe-area-inset-*). Desde o Chrome 135 no Android 15, a página pode ir de ponta a ponta, por baixo da barra de gestos:

  • Sem viewport-fit=cover, o Chrome reserva uma faixa embaixo e o inset fica em 0.
  • Com cover, a página passa por baixo da barra de gestos e o safe-area-inset-bottom informa a altura dela. O valor pode mudar quando a barra de baixo do Chrome se mexe.

O Chrome também criou o safe-area-max-inset-bottom, o maior valor que o inset vai ter. A recomendação do time do Chrome é usar o máximo no padding e compensar a diferença na posição, para o elemento não recalcular o layout a cada rolagem:

.barra {
  padding-bottom: env(safe-area-max-inset-bottom, 36px);
  bottom: calc(env(safe-area-inset-bottom, 0px) - env(safe-area-max-inset-bottom, 36px));
}

7. Erros de sintaxe que parecem “não funciona”

  • constant(safe-area-inset-bottom) só valeu no iOS 11.0 e 11.1. Pode apagar.
  • Erro de digitação não avisa. env(safe-area-inset-botom) sem reserva faz a declaração inteira voltar ao valor inicial da propriedade. Ao depurar, use uma reserva: env(safe-area-inset-bottom, 0px).
  • Não use env() em media query. A especificação permite, mas não conte com os navegadores.
  • Iframes: não conte com o inset dentro de um quadro incorporado. Coloque o padding na página que contém o iframe.

Ler os valores reais no aparelho

Desde o iOS 17.5, ler uma variável como --sab: env(safe-area-inset-bottom) com getPropertyValue pode dar 0. Leia um padding calculado:

const sonda = document.createElement('div');
sonda.style.cssText =
  'position:fixed;visibility:hidden;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)';
document.body.append(sonda);
const s = getComputedStyle(sonda);
console.table({ top: s.paddingTop, right: s.paddingRight, bottom: s.paddingBottom, left: s.paddingLeft });
sonda.remove();

Rode no Safari, depois no app instalado, depois em paisagem. Três contextos, três conjuntos de valores: é normal.

Testar com os insets reais enquanto programa

O Breakpane dá a cada iPhone e Android com moldura os valores reais de safe area, por esse mesmo comando do Chromium, no Windows e no Mac: ilha ou notch, indicador de início, laterais em paisagem, barra do Safari ou do Chrome no modo navegador e o modo App, com o estilo de status bar da sua página. Ele lê o viewport-fit como o iOS: sem cover, os insets ficam em 0 lá também. Os agentes de código podem rodar a auditoria de responsividade, que aponta conteúdo fora da área segura. O motor é o Chromium, não o WebKit: confira o resultado final num aparelho de verdade.

Outros guias

  1. SafariiPhone 17 Pro · 402 × 874
    iPhone 17 Pro no Safari com as cotas das barras de cima e de baixo
    100vh no Safari do iPhone: a barra de endereço, dvh, svh e lvh
  2. FormuláriosiPhone 16 · 393 × 852
    iPhone 16 com um app aberto e a cota de largura de 393 pontos
    iPhone dá zoom ao tocar no campo? Como tirar o zoom do input no Safari
  3. PWAiPhone 17 Pro · 402 × 874
    Tela de início do iPhone 17 Pro com o ícone de um PWA instalado
    Como testar um PWA no iPhone e no Android: modo standalone, ícone, splash e offline