Sticky Elements
Sticky Elements
8 reglasSticky CTA solo si página tiene más de 2 scroll-heights
Una barra sticky de CTA solo añade valor cuando el usuario no puede ver el botón primario original sin hacer scroll. En páginas cortas donde el CTA inline siempre está visible, el sticky duplica la acción sin ningún beneficio, añade ruido visual y compite con el contenido. La regla práctica: si la página tiene menos de 2 viewports de altura, omitir el sticky. Por encima de ese umbral, la barra sticky reduce el tiempo hasta el primer clic en el CTA un promedio del 34% en landing pages B2C.
Baymard Institute CTA Research 2023 · Luke Wroblewski "Mobile First" · ConversionXL Sticky Bar StudiesAmbos visibles a la vez.
Sticky siempre accesible.
Budget mobile: máx 100px total de elementos sticky del autor
En mobile, el espacio vertical es el recurso más escaso. El OS consume ~28px con la barra de estado, el navegador añade 44–56px de chrome, y la barra de dirección puede aparecer o desaparecer. El contenido disponible para el usuario es el que queda. Cuando el autor añade una nav sticky arriba y un CTA sticky abajo, el presupuesto disponible para ambos elementos combinados no debe superar 100px, de lo contrario el contenido real queda comprimido a menos del 60% de la pantalla. Prioridad: orientación (nav) sobre conversión (CTA); si hay que elegir, el CTA sticky cede primero.
Apple HIG Safe Áreas · Chrome Mobile UI Research 2022 · Luke Wroblewski "Mobile First" · Google Web.dev Layout ShiftsWCAG 2.4.11: ningún elemento con foco oculto por sticky
El criterio WCAG 2.4.11 (Focus Not Obscured, nivel AA en WCAG 2.2) exige que cuando un componente recibe foco por teclado, al menos una parte del indicador de foco sea visible. Un sticky header que oculta el elemento con foco detrás de él viola este criterio directamente. La solución canónica es declarar scroll-padding-top en el elemento raíz con un valor igual o ligeramente mayor que la altura del sticky header. Esto hace que el navegador ajuste automáticamente la posición de scroll al navegar con Tab, dejando el elemento enfocado completamente visible debajo del header.
Cumple WCAG 2.4.11
Falla WCAG 2.4.11
overflow:hidden en ancestro rompe sticky; fix = overflow:clip
El valor overflow:hidden en cualquier ancestro del elemento sticky establece un nuevo bloque contenedor para el posicionamiento, lo que hace que position:sticky se ancle a ese contenedor en lugar del viewport. El elemento parece "sticky" dentro del div padre pero no del viewport, lo que es exactamente el comportamiento incorrecto en casi todos los casos de uso. La solución es reemplazar overflow:hidden con overflow:clip, que preserva el recorte visual sin crear un nuevo contexto de posicionamiento. Para casos en flex/grid donde el sticky falla aunque no haya overflow, la propiedad align-self:start es el fix correcto.
hidden> ← problema
position:sticky
clip> ← fix
position:sticky
/* Aplicar en el elemento sticky, no en el padre */
.sidebar { align-self: start; position: sticky; top: 80px; }
Z-index map fijo; nunca valores arbitrarios
Los valores de z-index sin documentar producen regresiones de apilamiento que son extraordinariamente difíciles de depurar. Cuando un desarrollador escribe z-index:9999 para "estar seguro", cualquier capa futura que deba superarlo requiere valores aún más arbitrarios. La solución es definir un z-index map como tokens de diseño: cada capa nombrada tiene un valor fijo, conocido y documentado. Los elementos sticky generalmente viven entre 70 y 150; los modales y tooltips por encima de 200. El mapa debe publicarse en el design system y referenciarse como variables CSS o constantes JS.
flex/grid padre con align-items:stretch desactiva sticky; fix = align-self:start
El mecanismo de position:sticky requiere que el elemento tenga espacio para "deslizarse" dentro de su contenedor padre. Cuando el padre es un flex o grid container con el valor default align-items:stretch, el elemento sticky se estira para ocupar toda la altura del contenedor, eliminando ese espacio de deslizamiento. El resultado es un sticky que funciona solo al principio del scroll y luego queda "atrapado". La solución es aplicar align-self:start directamente en el elemento sticky (no en el padre), lo que le permite tener su altura natural y dejar espacio para scrollear.
Sticky section headers: solo si la lista tiene 5+ secciones y supera 3× el viewport
Los sticky section headers son un patrón de orientación para listas largas alfabéticas o categorizadas, como contactos, productos en catálogo, o transacciones financieras. En listas cortas el header se convierte en sticky, sale del viewport en menos de un scroll, y el usuario nunca lo aprovecha como indicador de posición. El costo cognitivo (más chrome, más complejidad de implementación) solo se justifica cuando la lista tiene suficiente densidad para que el usuario pierda orientación sin él, al menos 5 secciones con contenido sustancial y una longitud total superior a 3 veces la altura del viewport.
Apple iOS Contacts UX · Material Design 3 Lists · Baymard Institute List Navigation 2023 · Nielsen Norman Group "Alphabetical A-Z" 2019El header sticky orienta al usuario
mientras scrollea secciones largas.
Sticky header sale en <1 scroll.
No aporta orientación.
will-change:transform solo tras medir jank; nunca preventivo
will-change:transform promueve el elemento a su propia capa de compositing en la GPU, lo que puede eliminar jank en animaciones de sticky. Sin embargo, aplicarlo preventivamente en todos los sticky elements tiene un costo real: consume memoria GPU por cada elemento promovido, puede disparar repaints adicionales y complica el stacking context. La regla es: medir primero con DevTools Performance panel durante un scroll real, verificar que los frames superen 16ms durante la interacción del sticky, y solo entonces aplicar will-change. Además, transform en el mismo elemento sticky invalida el stacking context y puede romper el posicionamiento, aplicarlo solo al sticky directamente, nunca a un transform simultáneo.
CSS sticky es suficiente.
DevTools Performance
durante el scroll
investigar layout, paint
o JS bloqueante
will-change:transform
en scroll start.
Remover en scroll end.
transform y position:sticky al mismo tiempo en el mismo elemento, invalida el stacking context y puede romper el posicionamiento.
window.addEventListener('scroll', () => {
sticky.style.willChange = 'transform'
}, { passive: true })
// Limpiar al parar el scroll:
// (con debounce de ~150ms)
sticky.style.willChange = 'auto'
- R-154 Sticky CTA solo si página tiene más de 2 scroll-heights
- R-155 Budget mobile: máx 100px total de elementos sticky del autor
- R-156 WCAG 2.4.11: ningún elemento con foco oculto por sticky
- R-157 overflow:hidden en ancestro rompe sticky; fix = overflow:clip
- R-158 Z-index map fijo; nunca valores arbitrarios
- R-159 flex/grid padre con align-items:stretch desactiva sticky; fix = align-self:start
- R-160 Sticky section headers: solo si la lista tiene 5+ secciones y supera 3× el viewport
- R-161 will-change:transform solo tras medir jank; nunca preventivo