wandres.dev
STICKY · Cómo funciona de verdad

overflow: hidden, el asesino silencioso del sticky

Por qué cualquier overflow distinto de visible en un ancestro rompe el pegado, la regla de coerción entre ejes que casi nadie conoce, y por qué overflow: clip es la corrección exacta.

⏱ 16 min

De los tres fallos de sticky, este es el que más tiempo cuesta encontrar, porque la propiedad culpable puede estar a diez niveles de distancia, puesta hace dos años por otra persona, para resolver un problema que ya no existe. Y la explicación completa incluye una regla de la especificación que casi nadie conoce: escribir overflow-x: hidden cambia también el valor computado de overflow-y.

🎯 Al terminar esta lección sabrás
  • Explicar por qué un ancestro con overflow secuestra el pegado de un sticky.
  • Enunciar la regla de coerción entre overflow-x y overflow-y.
  • Sustituir overflow: hidden por overflow: clip cuando corresponda.
  • Localizar el ancestro culpable con una sola línea de consola.

Qué hace exactamente un overflow

Un elemento con overflow distinto de visible y de clip se convierte en un contenedor de scroll: define un scrollport, tiene una posición de scroll, y responde a los eventos de desplazamiento aunque su barra esté oculta o no haya nada que desbordar.

Un sticky se pega respecto a su contenedor de scroll más cercano. En el momento en que un ancestro se convierte en contenedor de scroll, deja de ser el viewport quien manda: manda ese ancestro. Y si ese ancestro no desborda —porque su contenido cabe entero— nunca hace scroll, su posición de scroll es siempre cero, el umbral nunca se cruza y el sticky nunca se mueve.

Esto es coherente y, visto desde la especificación, es lo único que podía ocurrir. Lo desconcertante no es la regla, es que la propiedad se aplique en un sitio y el efecto aparezca en otro.

Los valores que crean contenedor de scroll:

valor ¿crea contenedor de scroll? ¿recorta?
visible no no
hidden
scroll
auto
clip no

La última fila es la clave de esta lección. overflow: clip recorta igual que hidden pero no crea contenedor de scroll: no tiene posición de scroll, no se puede desplazar ni con JavaScript, y no secuestra a los sticky de dentro. Está disponible en los cuatro motores desde Safari 16, en septiembre de 2022.

La regla de coerción entre ejes

Aquí está la parte que explica el caso más común y más invisible. overflow es un atajo de overflow-x y overflow-y, y la especificación define una coerción entre los dos:

Si uno de los dos ejes tiene un valor computado que no es visible ni clip, y el otro tiene visible o clip, ese otro se computa a auto.

Traducido: no existe un elemento que recorte en un eje y desborde libremente en el otro si el recorte es de los que crean scroll. La razón es geométrica: un contenido que se sale por la derecha y es recortado, pero que puede salirse libremente por abajo, produciría un rectángulo de scroll indefinido.

La consecuencia práctica es demoledora para los sticky:

/* intención: evitar el scroll horizontal */
body { overflow-x: hidden; }

/* efecto real: overflow-y se computa a auto */
/* body pasa a ser contenedor de scroll y TODOS los sticky de la página lo toman como referencia */

Este patrón está en incontables bases de código, puesto para tapar un elemento que se sale de ancho. Y rompe todos los sticky de la página de golpe, incluidos los de componentes que funcionaban perfectamente en aislamiento.

La corrección exacta es cambiar el valor, no quitarlo:

body { overflow-x: clip; }

Como clip está en el conjunto visible o clip, la coerción no se dispara: overflow-y se queda en visible, body no se convierte en contenedor de scroll, y los sticky vuelven a referirse al viewport. El recorte horizontal sigue funcionando.

Conviene añadir la corrección de fondo: si algo se sale de ancho, lo correcto es arreglar el elemento que se sale. overflow-x: clip es un buen cinturón de seguridad, no un sustituto del diagnóstico.

flowchart TB
A[Escribes overflow-x hidden] --> B[La especificacion coerciona el otro eje]
B --> C[overflow-y computa a auto]
C --> D[El elemento se convierte en contenedor de scroll]
D --> E[Todos los sticky de dentro se refieren a el]
E --> F{Ese contenedor desborda}
F -->|No| G[Nunca hace scroll y el sticky no se pega]
F -->|Si| H[El sticky se pega respecto a el, no al viewport]
A --> I[Escribes overflow-x clip en su lugar]
I --> J[No hay coercion y overflow-y sigue visible]
J --> K[No hay contenedor de scroll y el sticky funciona]
style A fill:#f9e2af,color:#11111b
style G fill:#f38ba8,color:#11111b
style H fill:#f9e2af,color:#11111b
style I fill:#89b4fa,color:#11111b
style K fill:#a6e3a1,color:#11111b

El diagnóstico en una línea

Cuando un sticky no se pegue y sospeches de este caso, esta línea en la consola te da la lista de ancestros culpables sin abrir el inspector.

(function (sel) {
  let n = document.querySelector(sel);
  const malos = [];
  while (n && (n = n.parentElement)) {
    const s = getComputedStyle(n);
    if (s.overflowX !== 'visible' || s.overflowY !== 'visible') {
      malos.push({ nodo: n, x: s.overflowX, y: s.overflowY });
    }
  }
  console.table(malos);
})('.mi-sticky');

Lee overflowX y overflowY computados, no el atajo, precisamente porque la coerción se ve ahí y no en lo que escribiste. Un body con overflow-x: hidden aparecerá en la tabla con y: "auto", y esa columna es la prueba.

Los tres culpables habituales, por frecuencia:

overflow-x: hidden en html o body. Ya visto. Corrección: clip.

overflow: hidden para recortar esquinas redondeadas. Un contenedor con border-radius y una imagen dentro. Corrección: overflow: clip, que recorta igual.

overflow: auto en un panel que a veces desborda y a veces no. Aquí el sticky funciona de forma intermitente según el contenido, que es el síntoma más confuso de todos. Corrección: si el panel debe hacer scroll, el sticky de dentro debe pegarse respecto a él, y hay que ajustar los umbrales a la geometría del panel, no a la de la ventana.

clip existe porque hidden mezcló dos conceptos que no tenían por qué ir juntos

La historia de overflow: clip explica por qué el bug existe y por qué la corrección es tan limpia. overflow: hidden se especificó en CSS 2 con una definición que juntaba dos cosas independientes: recortar lo que se sale y poder desplazarse programáticamente. En 1998 esa unión parecía natural, porque si recortas contenido lo lógico es dejar una forma de llegar a él, aunque sea con JavaScript. Pero convertir un elemento en contenedor de scroll tiene una cascada de consecuencias que nadie pidió: establece un contexto de formato de bloque, contiene los flotantes, impide el colapso de márgenes hacia fuera, es el ancla de los sticky de dentro, participa en el encadenamiento de scroll, y desde 2019 también decide contra qué se resuelven scroll-padding y los umbrales de scroll-snap. Cada una de esas consecuencias es razonable por separado; juntas convierten overflow: hidden en una de las declaraciones con más efectos secundarios de todo el lenguaje. overflow: clip es la reparación: separa las dos ideas y te da el recorte puro, sin scrollport y sin ninguna de las consecuencias anteriores. La conclusión operativa que merece la pena adoptar: si escribes hidden y no esperas que ese elemento haga scroll nunca, lo que querías era clip. En 2026 ya no hay razón de compatibilidad para no hacerlo.

⚔️ Caza el overflow
  1. Pon overflow-x: hidden en body y comprueba en DevTools que overflow-y computa a auto.
  2. Verifica que con ese cambio todos los sticky de la página dejan de pegarse.
  3. Cámbialo a overflow-x: clip y confirma que vuelven a funcionar y que el recorte horizontal se mantiene.
  4. Ejecuta el script de diagnóstico sobre un sticky roto y localiza el ancestro culpable.
  5. Sustituye un overflow: hidden puesto para redondear esquinas por overflow: clip y comprueba que el recorte es idéntico.