Scroll snap en CSS

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.

Preguntar a la IA

Scroll snap en CSS

Respuesta generada con openai/gpt-5.4-nano. La IA puede cometer errores. Revisa siempre el resultado.

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

01

Snapport

El contenedor define los ejes de ajuste.

02

Intensidad

Mandatory y proximity cambian la atracción.

03

Padding

La región visible óptima se desplaza hacia dentro.

04

Eje bloque

El primer valor controla el eje de bloque.

05

Destino

Cada tarjeta crea su propia área de snap.

06

Eje inline

El segundo valor controla el eje inline.

07

Margin

El área de snap crece fuera del borde.

08

Parada

Always puede impedir saltarse un destino.

09

Teclado

Enfoca una tarjeta para llevarla a la vista.

HTML
<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>
Tailwind CSS
Algunas clases con valores arbitrarios pueden sustituirse por clases nativas, según el valor y la configuración de Tailwind CSS.
<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>
CSS
.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;
}