Web

Por qué env(safe-area-inset) te devuelve 0

Autorangel cruz
Publicado
Lectura7 min de lectura
Por qué env(safe-area-inset) te devuelve 0

Tienes una barra fija abajo. En un iPhone con indicador de home queda medio tapada, así que buscas la solución, encuentras la receta de siempre y la pegas:

.bottom-bar {
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
}

Recargas. No cambió nada.

No está rota. Le falta una pieza, y esa pieza tiene efectos secundarios que casi nadie menciona. Esto va de entender el mecanismo completo para poder decidir si de verdad lo quieres, porque en muchos sitios la respuesta correcta es no tocar nada.

Qué es la safe area

Los teléfonos dejaron de ser rectángulos limpios. Hay tres cosas que se comen pantalla:

  • El notch o la isla dinámica arriba
  • Las esquinas redondeadas
  • La barra indicadora de home abajo

La safe area es el rectángulo donde puedes poner contenido sin que nada lo tape. Todo lo de afuera es zona de riesgo.

Lo que el navegador hace sin que se lo pidas

Aquí está la parte que explica el misterio. Por defecto el navegador ya te protege: te achica el viewport para que quepa entero dentro de la zona segura.

El descriptor que controla esto es viewport-fit, definido en el CSS Round Display Module Level 1. Sus valores:

  • auto (el default): "no afecta al viewport de layout inicial, y la página entera es visible"
  • contain: "el viewport de layout inicial y el visual se fijan al rectángulo más grande inscrito en el display del dispositivo"
  • cover: "el viewport de layout inicial y el visual se fijan al rectángulo circunscrito de la pantalla física"

Traducido: por defecto tu viewport queda inscrito dentro de la pantalla, esquivando el notch. Con cover queda circunscrito, o sea que abarca la pantalla física completa, notch incluido.

Comparación de dos teléfonos. Por defecto, el viewport queda inscrito entre el notch y el indicador de home, sin tocarlos, y los safe area insets valen 0. Con viewport-fit=cover, el viewport cubre la pantalla física completa: las zonas del notch y del indicador quedan dentro de él y pasan a ser responsabilidad tuya, y los insets devuelven valores reales.

Por qué tu env() vale 0

Ya se ve solo. MDN define que los valores de safe-area-inset-* son "0 si el viewport es un rectángulo y no hay features ocupando espacio del viewport".

Y ahí está: por defecto el notch no ocupa espacio de tu viewport, porque tu viewport empieza más abajo. No hay nada que compensar, así que los cuatro valores son 0. Tu max(1rem, 0) resuelve a 1rem y la línea no hace absolutamente nada.

No es una interpretación mía. Hay un bug de WebKit cuyo título lo dice literalmente: safe-area-inset-left and safe-area-inset-right should be 0 [when] viewport-fit is not cover (but contain or auto).

La pieza que falta es esta:

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

Con eso el viewport pasa a cubrir la pantalla entera, los insets devuelven píxeles de verdad, y tu padding funciona.

Pero acabas de cambiar el trato: el navegador dejó de protegerte y ahora el responsable eres tú, en cada elemento del sitio.

Si tienes X, no hagas Y

Aquí es donde la mayoría de los tutoriales te deja tirado. Activar cover no es gratis. Estas son las reglas que uso para decidir.

Si tu fondo es un color sólido, no actives cover

Es el caso más común y el más malentendido. La gente activa cover para que el fondo llegue al borde físico.

No hace falta. El spec de backgrounds dice que "el fondo del elemento raíz se convierte en el fondo del canvas y su área de pintado se extiende para cubrir el canvas entero". En la práctica, en iOS la franja que queda fuera del viewport se pinta con el color de fondo de tu página. Si tu body tiene un color plano, ya tienes el efecto edge-to-edge sin ninguno de los riesgos.

Comprueba antes de asumir: si en tu teléfono no ves una banda de otro color, no tienes un problema que resolver.

Si tienes elementos con position: fixed, cover te obliga a inset cada uno

Navbar arriba, barra de acciones abajo, un botón flotante, un menú a pantalla completa, un banner de cookies. Cada uno de esos elementos vive pegado a un borde, y con cover los bordes ahora incluyen zona insegura.

Activar cover sin tocarlos convierte un sitio que funcionaba en uno con el logo debajo del notch. No es una mejora incremental: o los arreglas todos en el mismo commit, o introdujiste una regresión.

Si usas 100vw o full-bleed, revisa antes de activar

Con cover cambia lo que significa 100vw: pasa a medir la pantalla física completa. Cualquier sección a sangre que hoy calza perfecto empieza a extenderse por debajo del notch.

Ojo con el padding, que es donde se cae mucha gente: si tu contenedor sangrado tiene padding: 0 1rem, esos 16px no alcanzan para librar un notch lateral de unos 47px. El fondo se extiende bien, pero el texto queda tapado.

Si tienes imagen, video o degradado a sangre completa, ahí sí lo quieres

Este es el caso legítimo. Un hero con foto, un reproductor de video, un mapa, un lienzo de dibujo. Que el contenido visual toque el borde físico es una mejora real y perceptible, y justifica el trabajo de blindar el resto.

Si es una PWA en modo standalone, casi siempre lo quieres

Sin chrome del navegador, tu contenido es toda la pantalla y la sensación de app depende de llegar a los bordes. Es el escenario donde cover más rinde. Y también donde más obligatorio es hacer bien la tarea, porque no hay barra del navegador que te tape los errores.

Si tienes un bottom nav o un CTA sticky, cover sin env() es peor que nada

Estos dos se combinan mal por sí solos. Un bottom nav pegado a bottom: 0 con cover activado queda directamente debajo del indicador de home, con targets táctiles que el sistema se come.

Si activas cover, el padding con env() en esos elementos no es opcional.

La trampa: casi nadie prueba en horizontal

En vertical el notch está arriba y el indicador abajo, que es lo que todo el mundo revisa. En horizontal el notch se va a un costado, y ahí aparecen safe-area-inset-left y safe-area-inset-right, que en vertical valían 0.

Es el bug clásico de este tema: alguien activa cover, agrega padding arriba y abajo, prueba en vertical, y lo da por cerrado. Después llega un usuario en horizontal y tiene la primera palabra de cada título comida por el notch.

Si activas cover, gira el teléfono. No es opcional.

Si decides hacerlo, hazlo completo

Va todo en el mismo commit o no va:

  1. El meta viewport con viewport-fit=cover
  2. Padding horizontal en cada contenedor a sangre: padding-left: max(1rem, env(safe-area-inset-left)) y su espejo a la derecha
  3. Elementos fijos arriba: padding-top: max(…, env(safe-area-inset-top))
  4. Elementos fijos abajo: padding-bottom: max(…, env(safe-area-inset-bottom))
  5. Probar en vertical y en horizontal, en un dispositivo con notch real

El patrón max() importa: te garantiza tu padding de diseño cuando el inset es 0 (un teléfono sin notch, un escritorio) y lo agranda solo cuando hace falta. Si escribes padding-bottom: env(safe-area-inset-bottom) a secas, en cualquier pantalla rectangular tu elemento se queda sin padding.

env() también acepta fallback como segundo argumento, útil para navegadores viejos que no conocen la variable:

padding-bottom: max(1rem, env(safe-area-inset-bottom, 0px));

Y si te cruzas con constant(safe-area-inset-bottom) en algún snippet antiguo, ignóralo. Fue la sintaxis original de iOS 11.0 y WebKit la reemplazó por env() en iOS 11.2. Está muerta.

La regla mental

Antes de copiar la receta, pregúntate qué estás resolviendo:

  • ¿Ves una franja de color distinto en el borde? Probablemente no, y entonces no tienes un problema.
  • ¿Tienes contenido visual que quieres que toque el borde físico? Ahí sí, y vale el trabajo.
  • ¿Estás copiando env() porque lo viste en un blog? Comprueba primero si te devuelve algo distinto de 0.

El default del navegador no es una limitación que haya que vencer. Es una decisión de diseño sensata que te resuelve gratis el 90% de los casos. viewport-fit=cover es renunciar a esa red de seguridad a cambio de control, y solo conviene cuando de verdad vas a usar ese control.

La peor combinación posible es la intermedia: activarlo porque sonaba bien, blindar solo la mitad de los elementos, y probar solo en vertical.