CSS scroll snap

Explore how a scroll area settles into predictable positions. Change the axis, strictness, block and inline alignment, and the spacing around the snapport and each target.

Ask AI

CSS scroll snap

Response generated with openai/gpt-5.4-nano. AI can make mistakes. Always review the result.

Drag, use the wheel, or focus the cards with the keyboard to test every snap position.

Dashed line: snapport reduced by scroll-paddingDotted line: area expanded by scroll-margin

01

Snapport

The container defines the snapping axes.

02

Strictness

Mandatory or proximity changes the pull.

03

Padding

The optimal viewing region moves inward.

04

Block axis

The first alignment value controls block.

05

Snap target

Each card creates its own snap area.

06

Inline axis

The second alignment value controls inline.

07

Margin

The snap area grows beyond the border box.

08

Snap stop

Always can prevent skipping a target.

09

Keyboard

Focus a card to bring it into view.

HTML
<div class="snapport" aria-label="Interactive CSS scroll snap area" tabindex="0">
  <article
    class="snap-item"
    style="--item-color: #ffb38a"
    tabindex="0"
  >
    <span>01</span>
    <h2>Snapport</h2>
    <p>The container defines the snapping axes.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #72d6c9"
    tabindex="0"
  >
    <span>02</span>
    <h2>Strictness</h2>
    <p>Mandatory or proximity changes the pull.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #b6a8ff"
    tabindex="0"
  >
    <span>03</span>
    <h2>Padding</h2>
    <p>The optimal viewing region moves inward.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #ffd166"
    tabindex="0"
  >
    <span>04</span>
    <h2>Block axis</h2>
    <p>The first alignment value controls block.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #f7a6c4"
    tabindex="0"
  >
    <span>05</span>
    <h2>Snap target</h2>
    <p>Each card creates its own snap area.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #8ecae6"
    tabindex="0"
  >
    <span>06</span>
    <h2>Inline axis</h2>
    <p>The second alignment value controls inline.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #a7df9b"
    tabindex="0"
  >
    <span>07</span>
    <h2>Margin</h2>
    <p>The snap area grows beyond the border box.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #f6bd60"
    tabindex="0"
  >
    <span>08</span>
    <h2>Snap stop</h2>
    <p>Always can prevent skipping a target.</p>
  </article>
  <article
    class="snap-item"
    style="--item-color: #c7a6ff"
    tabindex="0"
  >
    <span>09</span>
    <h2>Keyboard</h2>
    <p>Focus a card to bring it into view.</p>
  </article>
</div>
Tailwind CSS
Some classes with arbitrary values can be replaced by native classes, depending on the value and your Tailwind CSS configuration.
<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="Interactive CSS scroll snap area" 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>The container defines the snapping axes.</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>Strictness</h2>
    <p>Mandatory or proximity changes the pull.</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>The optimal viewing region moves inward.</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>Block axis</h2>
    <p>The first alignment value controls block.</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>Snap target</h2>
    <p>Each card creates its own snap area.</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>Inline axis</h2>
    <p>The second alignment value controls 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>The snap area grows beyond the border box.</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>Snap stop</h2>
    <p>Always can prevent skipping a target.</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>Keyboard</h2>
    <p>Focus a card to bring it into view.</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;
}