Snapport
El contenedor define los ejes de ajuste.
Explora cómo encaja un área de scroll en posiciones predecibles. Cambia el eje, la intensidad, la alineación de bloque e inline y los espacios del snapport y de cada destino.
Arrastra, usa la rueda o enfoca las tarjetas con el teclado para comprobar cada posición de snap.
Línea discontinua: snapport reducido por scroll-paddingLínea de puntos: área ampliada por scroll-margin
El contenedor define los ejes de ajuste.
Mandatory y proximity cambian la atracción.
La región visible óptima se desplaza hacia dentro.
El primer valor controla el eje de bloque.
Cada tarjeta crea su propia área de snap.
El segundo valor controla el eje inline.
El área de snap crece fuera del borde.
Always puede impedir saltarse un destino.
Enfoca una tarjeta para llevarla a la vista.
<div class="snapport" aria-label="Área interactiva de scroll snap en CSS" tabindex="0">
<article
class="snap-item"
style="--item-color: #ffb38a"
tabindex="0"
>
<span>01</span>
<h2>Snapport</h2>
<p>El contenedor define los ejes de ajuste.</p>
</article>
<article
class="snap-item"
style="--item-color: #72d6c9"
tabindex="0"
>
<span>02</span>
<h2>Intensidad</h2>
<p>Mandatory y proximity cambian la atracción.</p>
</article>
<article
class="snap-item"
style="--item-color: #b6a8ff"
tabindex="0"
>
<span>03</span>
<h2>Padding</h2>
<p>La región visible óptima se desplaza hacia dentro.</p>
</article>
<article
class="snap-item"
style="--item-color: #ffd166"
tabindex="0"
>
<span>04</span>
<h2>Eje bloque</h2>
<p>El primer valor controla el eje de bloque.</p>
</article>
<article
class="snap-item"
style="--item-color: #f7a6c4"
tabindex="0"
>
<span>05</span>
<h2>Destino</h2>
<p>Cada tarjeta crea su propia área de snap.</p>
</article>
<article
class="snap-item"
style="--item-color: #8ecae6"
tabindex="0"
>
<span>06</span>
<h2>Eje inline</h2>
<p>El segundo valor controla el eje inline.</p>
</article>
<article
class="snap-item"
style="--item-color: #a7df9b"
tabindex="0"
>
<span>07</span>
<h2>Margin</h2>
<p>El área de snap crece fuera del borde.</p>
</article>
<article
class="snap-item"
style="--item-color: #f6bd60"
tabindex="0"
>
<span>08</span>
<h2>Parada</h2>
<p>Always puede impedir saltarse un destino.</p>
</article>
<article
class="snap-item"
style="--item-color: #c7a6ff"
tabindex="0"
>
<span>09</span>
<h2>Teclado</h2>
<p>Enfoca una tarjeta para llevarla a la vista.</p>
</article>
</div><div class="grid grid-cols-[repeat(3,72%)] auto-rows-[72%] gap-6 inline-[min(100%,36rem)] block-96 m-0 p-0 overflow-auto overscroll-contain snap-both snap-mandatory scroll-p-[32px]" aria-label="Área interactiva de scroll snap en CSS" tabindex="0">
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#ffb38a_72%,#172033)] bg-[#ffb38a]"
tabindex="0"
>
<span>01</span>
<h2>Snapport</h2>
<p>El contenedor define los ejes de ajuste.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#72d6c9_72%,#172033)] bg-[#72d6c9]"
tabindex="0"
>
<span>02</span>
<h2>Intensidad</h2>
<p>Mandatory y proximity cambian la atracción.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#b6a8ff_72%,#172033)] bg-[#b6a8ff]"
tabindex="0"
>
<span>03</span>
<h2>Padding</h2>
<p>La región visible óptima se desplaza hacia dentro.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#ffd166_72%,#172033)] bg-[#ffd166]"
tabindex="0"
>
<span>04</span>
<h2>Eje bloque</h2>
<p>El primer valor controla el eje de bloque.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#f7a6c4_72%,#172033)] bg-[#f7a6c4]"
tabindex="0"
>
<span>05</span>
<h2>Destino</h2>
<p>Cada tarjeta crea su propia área de snap.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#8ecae6_72%,#172033)] bg-[#8ecae6]"
tabindex="0"
>
<span>06</span>
<h2>Eje inline</h2>
<p>El segundo valor controla el eje inline.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#a7df9b_72%,#172033)] bg-[#a7df9b]"
tabindex="0"
>
<span>07</span>
<h2>Margin</h2>
<p>El área de snap crece fuera del borde.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#f6bd60_72%,#172033)] bg-[#f6bd60]"
tabindex="0"
>
<span>08</span>
<h2>Parada</h2>
<p>Always puede impedir saltarse un destino.</p>
</article>
<article
class="box-border grid content-end gap-2 p-[clamp(20px,4vw,32px)] border rounded-2xl text-[#172033] scroll-m-[12px] snap-center snap-normal *:m-0 [&_h2]:text-[clamp(1.75rem,1rem_+_3vw,3rem)] [&>span]:font-extrabold focus-visible:outline-4 focus-visible:outline-[#172033] focus-visible:-outline-offset-8 border-[color-mix(in_oklch,#c7a6ff_72%,#172033)] bg-[#c7a6ff]"
tabindex="0"
>
<span>09</span>
<h2>Teclado</h2>
<p>Enfoca una tarjeta para llevarla a la vista.</p>
</article>
</div>.snapport {
display: grid;
grid-template-columns: repeat(3, 72%);
grid-auto-rows: 72%;
gap: 1.5rem;
inline-size: min(100%, 36rem);
block-size: 24rem;
margin: 0;
padding: 0;
overflow: auto;
overscroll-behavior: contain;
scroll-snap-type: both mandatory;
scroll-padding: 32px;
}
.snap-item {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
padding: clamp(20px, 4vw, 32px);
border: 1px solid color-mix(in oklch, var(--item-color) 72%, #172033);
border-radius: 1rem;
background: var(--item-color);
color: #172033;
scroll-margin: 12px;
scroll-snap-align: center;
scroll-snap-stop: normal;
}
.snap-item > * {
margin: 0;
}
.snap-item h2 {
font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}
.snap-item > span {
font-weight: 800;
}
.snap-item:focus-visible {
outline: 4px solid #172033;
outline-offset: -8px;
}.snapport {
display: grid;
grid-template-columns: repeat(3, 72%);
grid-auto-rows: 72%;
gap: 1.5rem;
inline-size: min(100%, 36rem);
block-size: 24rem;
margin: 0;
padding: 0;
overflow: auto;
overscroll-behavior: contain;
scroll-snap-type: both mandatory;
scroll-padding: 32px;
> .snap-item {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
padding: clamp(20px, 4vw, 32px);
border: 1px solid color-mix(in oklch, var(--item-color) 72%, #172033);
border-radius: 1rem;
background: var(--item-color);
color: #172033;
scroll-margin: 12px;
scroll-snap-align: center;
scroll-snap-stop: normal;
> * {
margin: 0;
}
h2 {
font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}
> span {
font-weight: 800;
}
&:focus-visible {
outline: 4px solid #172033;
outline-offset: -8px;
}
}
}Guía educativa de scroll-snap
Scroll snap ayuda a que una galería, lista de tarjetas o secuencia de secciones termine de desplazarse en posiciones útiles. Aprende a separar el área de scroll de sus destinos y a elegir un ajuste que conserve la lectura y la navegación.
El navegador alinea el área de un destino con una región del contenedor. El layout sigue siendo responsabilidad de Flexbox, Grid o el flujo normal.
Primero necesitas overflow real. Una fila de tarjetas puede usar display: flex, elementos que no se encojan y overflow: auto. scroll-snap-type se aplica a ese contenedor; no crea el layout, una barra de scroll ni contenido desbordado por sí solo.
Cada destino aporta una posición mediante scroll-snap-align. El valor inicial none no aporta ninguna. Los destinos se asocian al ancestro de scroll pertinente más cercano: poner la propiedad en un wrapper exterior no configura un scroller interior.
La intensidad depende de lo que se necesita leer, no solo del efecto visual.
proximityÚtil para tarjetas y lectura continua. El navegador puede ajustar cuando terminas cerca de un destino y permite detenerte entre posiciones. La distancia que considera cercana depende del navegador y del gesto; no hay un umbral de píxeles que puedas configurar.
mandatoryÚtil para una secuencia con destinos claros y contenido controlado. Al terminar el scroll, el contenedor debe quedar en una posición válida si existe alguna. Revisa alturas variables, zoom y teclado antes de aplicarlo a contenido de lectura.
Un destino mayor que el snapport no es automáticamente ilegible: la especificación permite recorrer su interior mientras cubre esa región. Los problemas aparecen con contenido fuera de las áreas de snap, alturas rígidas o posiciones que no encajan con la lectura. Para artículos largos, empieza con proximity y prueba que puedes alcanzar principio, centro y final con texto ampliado.
scroll-snap-stop: normal permite atravesar destinos en un desplazamiento; always impide pasar de largo por una posición durante operaciones que recorren posiciones intermedias. No cambia proximity por mandatory ni obliga a detenerse en cada destino cuando saltas directamente a una posición, por ejemplo con un enlace.
El eje de desplazamiento, la posición de ajuste y el espacio visual son decisiones diferentes.
x e y son ejes físicos; inline y block siguen el modo de escritura. En horizontal-tb, inline corresponde al horizontal y block al vertical. both permite ajuste en los dos ejes y requiere un escenario con scroll en ellos; no convierte una fila en una cuadrícula.
start, center y end alinean el inicio, centro o final del área del destino con el snapport. Un valor de scroll-snap-align se aplica a ambos ejes. Con dos valores, el primero es block y el segundo inline: none start crea una posición solo en inline, no significa «x e y».
scroll-padding pertenece al contenedor y reduce su región de alineación. scroll-margin pertenece al destino y amplía su área de snap. Ninguno ocupa espacio en el layout: padding, margin y gap siguen siendo necesarios para separar visualmente las cajas.
Fragmento para una lista vertical con overflow real: 4rem reservan el espacio de la cabecera y 0.5rem añaden separación al destino. Ajusta la reserva al tamaño real de la cabecera, también con zoom.
En un ajuste start, esas distancias se combinan para separar el destino del borde. Los límites del scroll siguen contando: la primera o última tarjeta puede no alcanzar la alineación ideal. scroll-padding no añade recorrido vacío al principio o al final; añade espacio de layout si el diseño lo necesita.
Dos listas con las mismas tarjetas y medidas permiten comparar la intensidad. Cambia solo mandatory por proximity en el CSS.
Las tarjetas conservan enlaces HTML reales y dejan asomar la siguiente. Cada lista tiene nombre accesible, foco propio y barra nativa, y funciona sin JavaScript. El CSS muestra valores explícitos de color, radio y espaciado; light-dark() conserva ambos temas con color-scheme. Puedes adaptar esos valores a tu proyecto.
Mantén visible que hay más contenido y conserva una forma directa de desplazarse.
Abrir el labReserva la región donde un destino debe quedar visible al alinearse.
Abrir el labAñade navegación explícita cuando la secuencia necesita botones o marcadores.
Abrir el labMantén visible que hay más contenido y conserva una forma directa de desplazarse.
Abrir el labReserva la región donde un destino debe quedar visible al alinearse.
Abrir el labAñade navegación explícita cuando la secuencia necesita botones o marcadores.
Abrir el labNo ocultes contenido ni canceles el comportamiento nativo de los enlaces para conseguir snap. Prueba teclado, tacto y rueda por separado; comprueba que el foco permanece visible. Evita autoplay y scroll suave innecesario; si añades animación, respeta prefers-reduced-motion.
Diagnostica primero el contenedor y después los destinos. Un ajuste declarado puede no tener ningún recorrido al que aplicarse.
Separa el ajuste básico de las APIs que permiten construir navegación y observar sus cambios.
Las propiedades básicas scroll-snap-type, scroll-snap-align, scroll-snap-stop, scroll-padding y scroll-margin están ampliamente disponibles según MDN. Ese estado no garantiza la misma física, distancia de proximity o posición final en cada navegador.
Si se ignoran las declaraciones de snap, las recetas mantienen scroll normal y enlaces utilizables. No necesitan un polyfill para acceder al contenido. La preview muestra la implementación de tu navegador, no una certificación entre motores.
Scroll snap aporta posiciones; un carrusel añade navegación y estado. No genera botones, marcadores ni anuncios accesibles. El soporte de scroll buttons, scroll markers, eventos de snap o consultas de estado debe revisarse por separado; aquí no se usan esas piezas.