Saltar al contenido

CSS, de a poco

Curso de CSS

Scroll

Lección 28 de 37 · 5 min

El navegador ya sabe scrollear. Con unas pocas propiedades le decís dónde frenar, cuánto lugar dejar arriba de un ancla, qué hacer al llegar al final y cuánto espacio reservar para la barra.

Frenar en un punto

En Overflow viste que overflow: auto crea un contenedor con scroll. Con scroll-snap-type en ese contenedor y scroll-snap-align en los hijos, el scroll termina siempre con un hijo alineado, como en un carrusel.

  1. Tiposcroll-snap-typeVa en el contenedor: un eje (x, y, both, inline o block) y una intensidad. mandatory frena siempre en un punto; proximity, solo si el scroll terminó cerca de uno.
  2. Alineaciónscroll-snap-alignVa en cada hijo y dice qué borde se alinea: start, center o end.
  3. Margen de frenadoscroll-paddingAchica la zona de alineación, el snapport: con 24px cada paso frena a 24px del borde y se asoma el anterior. Sumale el mismo padding-inline, así el primero y el último también llegan a esa línea.
  4. Sin saltosscroll-snap-stop: alwaysNo deja saltear hijos con un deslizamiento rápido: frena en el siguiente aunque el gesto diera para más.

Scrolleá la receta con el trackpad, el dedo o las flechas. La línea naranja es donde frena el borde alineado; la lectura de arriba dice qué paso quedó ahí y a cuántos píxeles.

scroll-snap-type
paso 1 de 6 · a 0px de la línea

Pan de campo con masa madre

6 pasos · 7 h

01

Autólisis

Mezclá 500 g de harina con 350 g de agua y dejá reposar tapado.

40 min

02

Masa madre y sal

Sumá 100 g de masa madre activa y 10 g de sal. Amasá hasta integrar.

5 min

03

Pliegues

Cuatro series de pliegues, una cada media hora, con las manos húmedas.

2 h

04

Fermentación

Dejala hasta que crezca la mitad de su volumen y tenga burbujas.

3 h

05

Formado

Boleá, dale tensión y pasala a un banneton enharinado.

15 min

06

Horno

20 min a 250 °C con tapa y 25 min más sin tapa.

45 min

align
receta.css
.receta {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: 24px;
padding-inline: 24px;
}
.paso {
flex: none;
scroll-snap-align: start;
}
receta.html
<div class="flex overflow-x-auto snap-x snap-mandatory scroll-px-6 px-6" tabindex="0" role="region" aria-label="Pasos de la receta">
<section class="w-[72%] flex-none snap-start">…</section>
<section class="w-[72%] flex-none snap-start">…</section>
…
</div>

Con proximity el scroll puede quedar entre dos pasos; con none no hay línea, y el desvío es el que dejaste. Los botones usan scrollBy(): el snap también ordena los scrolls de JavaScript.

Es Baseline desde hace años: scroll-snap-type anda completo desde Chrome 69, Firefox 99 y Safari 11, y scroll-snap-stop desde Chrome 75, Firefox 103 y Safari 15.

Anclas bajo un encabezado fijo

Cuando un enlace salta a #limpieza, el navegador pone el título arriba de todo. Si hay un encabezado sticky, el título queda tapado. Hay dos formas de dejarle lugar:

  1. En el contenedorscroll-padding-topVa en el contenedor que scrollea y vale para todos los destinos de una vez. Es la que conviene para un encabezado fijo.
  2. En el destinoscroll-margin-topUn margen que solo cuenta al llevar el elemento a la vista. Sirve cuando uno necesita más aire que los demás.

Las dos afectan a las anclas, a scrollIntoView(), al foco que lleva un elemento a la vista y al snap. El encabezado del manual de la cafetera mide 48px: con 56px el título queda abajo, con 8px de aire.

Un manual con un encabezado fijo de 48px, después de saltar a Limpieza. Sin nada, el título queda arriba de todo, tapado por el encabezado; con scroll-padding-top de 56px queda justo debajo.

Probalo en el manual: tocá un enlace del índice y cambiá cómo se le deja lugar al encabezado.

Dejar lugar al encabezado con
tocá un enlace del índice

Cafetera italiana de 6 tazas

Manual de uso. Leelo entero antes de usarla por primera vez.

1.Molienda

Usá café molido medio, más grueso que el de espresso. Si es muy fino, el agua no pasa y se abre la válvula.

Para 6 tazas van unos 18 g de café, lo que entra en el filtro al ras.

2.Carga

Llená la base con agua caliente justo por debajo de la válvula, sin taparla.

Poné el café en el filtro sin apretarlo y nivelalo con el dedo antes de enroscar.

3.En el fuego

Fuego bajo y la tapa abierta. Cuando el café sale claro y con burbujas, retirala.

Pasá la base por agua fría para cortar la extracción y que no se queme.

4.Limpieza

Solo agua caliente, sin detergente: la capa de aceite protege el aluminio.

Cambiá la junta de goma una vez por año o cuando pierda vapor por la rosca.

Garantía

Dos años contra defectos de fabricación. La junta y el filtro son repuestos de desgaste.

overscroll-behavior
manual.css
.manual {
overflow-y: auto;
scroll-padding-top: 56px;
overscroll-behavior: contain;
}
@media (prefers-reduced-motion: no-preference) {
.manual {
scroll-behavior: smooth;
}
}
.indice {
position: sticky;
top: 0;
height: 48px;
}
manual.html
<div class="h-72 overflow-y-auto scroll-pt-14 overscroll-contain motion-safe:scroll-smooth" tabindex="0" role="region" aria-label="Manual de la cafetera">
<nav class="sticky top-0 h-12">
<a href="#molienda">Molienda</a>
<a href="#carga">Carga</a>
<a href="#fuego">Fuego</a>
<a href="#limpieza">Limpieza</a>
</nav>
<h3 id="molienda">Molienda</h3>
…
</div>

scroll-behavior: smooth anima esos saltos: las anclas, scrollTo() y scrollIntoView(). No toca el scroll de la rueda ni del dedo. Ponelo dentro de prefers-reduced-motion: no-preference, así quien pidió menos movimiento salta sin animación.

styles.css
html {
scroll-padding-top: 4rem; /* el alto del encabezado, más aire */
}
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}

scroll-padding y scroll-margin son Baseline desde 2021 (Chrome 69, Firefox 68, Safari 14.1). scroll-behavior es Baseline desde 2022, con Safari 15.4.

Qué pasa al llegar al final

Si scrolleás un panel hasta el final y seguís, el scroll pasa a la página. Eso es el encadenamiento de scroll. En un menú lateral o en un chat molesta: llegás al final de la lista y se te mueve todo lo de atrás.

  1. EncadenaautoEl comportamiento normal: pasa el scroll a la página y, en el celular, hace el rebote y el gesto de deslizar para recargar.
  2. Corta la cadenacontainEl scroll se queda en el panel, pero el rebote sigue estando adentro.
  3. Corta y no rebotanoneCorta la cadena y saca el rebote. En el html, overscroll-behavior-y: none desactiva el deslizar para recargar del navegador.
Una página con un chat, scrolleado hasta el final del chat. Con auto el scroll sigue con la página, que se corre hacia arriba; con contain frena al final del chat y la página no se mueve.

Probalo con el manual de la cafetera: con contain, la rueda se queda en el panel al llegar a la garantía; con auto, sigue con la página.

chat.css
.chat {
overflow-y: auto;
overscroll-behavior: contain;
}
/* una app con su propio gesto de recargar */
html {
overscroll-behavior-y: none;
}

contain y none andan en Chrome 63, Firefox 59 y Safari 16, pero solo en contenedores que de verdad tienen scroll. Chrome 144 y Firefox 150 lo extendieron a contenedores sin contenido de más y Safari todavía no, por eso web-features no lo cuenta como Baseline. Para el caso de siempre, un panel que scrollea, podés usarlo tranquilo.

La barra de scroll

En Windows, en Linux y en macOS con la barra siempre visible, la barra ocupa lugar: le saca ancho al contenido. Cuando una lista crece y aparece la barra, todo se corre unos 15px a la izquierda. Con barras superpuestas (macOS por defecto, los celulares) no pasa, porque la barra flota encima.

  1. Reservar el lugarscrollbar-gutter: stableGuarda el lugar de la barra aunque no haga falta, así el contenido no salta cuando aparece. Con barras superpuestas no hace nada.
  2. Anchoscrollbar-widthAcepta auto, thin o none. none la esconde pero sigue scrolleando: dejá otra pista de que hay más contenido.
  3. Colorscrollbar-colorToma dos colores: el de la barra que se arrastra y el del riel.

Con auto, compará la columna del andén en los horarios de 4 y de 12 trenes. Después probá con stable. La franja naranja es el ancho que la barra le saca al contenido; con barras superpuestas mide 0.

scrollbar-gutter
la barra no ocupa lugar

Retiro → Tigre

4 trenes

SaleDestinoAndén
06:12Tigre3
06:42Tigre3
07:12Victoria4
07:42Tigre3

Retiro → Tigre

12 trenes

SaleDestinoAndén
06:12Tigre3
06:42Tigre3
07:12Victoria4
07:42Tigre3
08:12Tigre4
08:42Victoria3
09:12Tigre4
09:42Tigre3
10:12Victoria4
10:42Tigre3
11:12Tigre4
11:42Victoria3
width
horarios.css
.horarios {
overflow-y: auto;
scrollbar-gutter: auto;
scrollbar-width: auto;
}
horarios.html
<div class="h-49 overflow-y-auto" tabindex="0" role="region" aria-label="Horarios de los sábados">
<table>…</table>
</div>

Son más nuevas: scrollbar-gutter y scrollbar-width son Baseline 2024 y scrollbar-color es Baseline 2025, con Safari 26.2. Si un navegador no las conoce, dibuja la barra de siempre: no se rompe nada.

En Tailwind

Tailwind tiene clases para el snap, el padding y el margin de scroll, el scroll suave y el overscroll. Para la barra no hay: van como propiedades arbitrarias entre corchetes.

CSSTailwind
scroll-snap-type: x mandatorysnap-x snap-mandatory
scroll-snap-align: startsnap-start
scroll-snap-stop: alwayssnap-always
scroll-padding-inline: 24pxscroll-px-6
scroll-padding-top: 56pxscroll-pt-14
scroll-behavior: smoothmotion-safe:scroll-smooth
overscroll-behavior: containoverscroll-contain
scrollbar-gutter: stable[scrollbar-gutter:stable]
scrollbar-width: thin[scrollbar-width:thin]
pagina.html
<html class="scroll-pt-16 motion-safe:scroll-smooth">
<header class="sticky top-0 h-14">…</header>
<!-- carrusel: frena en cada paso, a 24px del borde -->
<div class="flex overflow-x-auto snap-x snap-mandatory
scroll-px-6 overscroll-x-contain"
tabindex="0" role="region" aria-label="Pasos">
<section class="w-[72%] flex-none snap-start">…</section>
</div>
<!-- lista que crece sin correr el contenido -->
<div class="h-56 overflow-y-auto [scrollbar-gutter:stable]">…</div>
</html>