Scroll✨ Premium

Mixed Content Snap

A 280 px viewport with scroll-snap-type: y — proximity, not mandatory — and five sections of 280, 140, 140, 80 and 280 px aligned to start: snap points at 0, 280, 420, 560 and 640 px, a section shorter than the viewport never fills it alone, and one wheel notch from the top stops at 100 px, between two. Zero JavaScript.

CSSMixedSnap

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Structured weather bulletin on mobile — short alert, full-height hourly, 7-day at half — Mixed Content Snap example 1

① Structured weather bulletin on mobile — short alert, full-height hourly, 7-day at half

WhenScreen of a weather app: an alert (short, urgent section), the hourly forecast (full section), then the 7-day forecast (half section) — height reflects informational weight.
WhyThe alert section must snap faster in a reduced space: at 72 px it is read and left in one gesture, and the hourly forecast takes the whole screen. Vertical Snap (fx-0593), identical sections, flattens the hierarchy and gives the critical alert as much room as secondary information.
Settings.sn-mixed-container viewport at 296 px (the phone screen); alert .sn-quarter at 72 px, hourly .sn-full at 296 px, 7-day .sn-half at 148 px with scroll-snap-align: end so the last stop (220 px) is reachable; backgrounds carried by the classes, proximity kept, “Scroll ↓” cue kept inside the screen.
Physiotherapy session report — title and score at a quarter, ROM chart full, recommendations at half — Mixed Content Snap example 2

② Physiotherapy session report — title and score at a quarter, ROM chart full, recommendations at half

WhenA therapist reviews a session report on a tablet: title and overall score (quarter section), joint range-of-motion chart (full section), patient recommendations (half section) — the reading rhythm follows information density.
WhySection height encodes content density: the score is read at a glance, the chart needs the whole screen. Snap + Progress (fx-0597) levels every section to the same height and loses the clinical hierarchy of the report — and its index script assumes equal sections.
SettingsViewport at 272 px; header + score .sn-quarter at 68 px, chart .sn-full at 272 px, recommendations .sn-half at 136 px with end; scroll-snap-type: y mandatory (a report never stops between two blocks); scroll-behavior: auto under prefers-reduced-motion.
Interactive newsletter with distinct editorial weights — masthead at a quarter, feature full, two briefs at half — Mixed Content Snap example 3

③ Interactive newsletter with distinct editorial weights — masthead at a quarter, feature full, two briefs at half

WhenAn online magazine sends a rich HTML newsletter: masthead (quarter section), lead article (full section), two briefs (half sections) — snapping reflects editorial importance.
WhyThe visual weight of the sections communicates editorial importance: size speaks before the text does. Scroll Snap Sections (fx-0584), identical sections with navigation dots, cannot tell a 500-word article from a three-word headline.
SettingsViewport at 280 px as in the code; masthead .sn-quarter at 70 px, article .sn-full at 280 px, two briefs .sn-half at 140 px — the final stop (350 px) shows both briefs together; proximity kept, the reader can pause mid-article; light backgrounds specific to the letter.

How it works

The sold HTML is a .demo-preview (centered flex, min-height: 300px, position: relative, #0a0a0f background) holding .sn-mixed-containerwidth: 100%, height: 280px, overflow-y: auto, scroll-snap-type: y, scroll-behavior: smooth — and, next to it, the .sdc-scroll-hint cue “Scrollez ↓”: absolute, 8 px from the bottom of the .demo-preview, 10.4 px, white at 30%, opacity pulsing from .3 to .7 over 2 s (sdcHintPulse). Inside the container, five .sn-mixed-section with scroll-snap-align: start, centered flex column, padding: 16px, whose height comes from a second class: .sn-full 280 px (exactly the viewport), .sn-half 140 px, .sn-quarter 80 px. The sold order — full, half, half, quarter, full — makes 920 px of content for 640 px of travel. Backgrounds are five 135° gradients assigned by :nth-child(1) to (5), from #1e1b4b to #8b5cf6: they follow the rank, not the size. Text: h4 .85 rem (13.6 px) white, p .68 rem (10.9 px) white at 60%; every h4 carries an inline font-family: Inter, …, a catalog leftover.

The snap points are the section tops: 0, 280, 420, 560 and 640 px — the last one coincides with the end of travel because the final section is as tall as the viewport. This is where mixed heights change the reading: a section shorter than the viewport never fills it alone. At 280, the two half sections share the screen; at 420, the second half, the quarter and 60 px of the final one; at 560, the quarter takes the top and the final section the remaining 200 px. The rhythm is the gap between stops: 280, 140, 140, 80. And scroll-snap-type: y with no second keyword means proximity, not mandatory: snapping only acts within reach of a point. Measured in Chromium 151: a position left 93 px from a point is pulled to it, at 94 px it stays — the threshold is one third of the viewport (280 / 3). Consequence: one wheel notch (100 px) from the top stops at 100 px and stays there (still at 100 px two seconds later); two notches within 120 ms reach 280; after that every notch lands within reach of a point: 280 → 420 → 560 → 640. On a trackpad, 5 × 20 px are pulled back to 0, 13 × 20 px stop at 143 px, in between.

scroll-behavior: smooth only concerns programmatic scrolls (scrollTo, anchors, keys): measured 0 → 640 px in 395 ms, 226 px covered at 100 ms. No JavaScript: no listener, nothing to remove, the snap animation is the browser's. Three limits belong to the code itself. The :nth-child backgrounds: a sixth section has none (measured background: none). The last section: switched to .sn-half, its top (640) exceeds the travel (500) and it never aligns to the top (measured: stop at 500). The cue: it overlaps the container's last 10 pixels (280–292 px versus 10–290) and, without the positioned .demo-preview, drops to the bottom of the window (measured at 479 px for a container ending at 280). No site CSS variable, no console error.

Accessibility

  • prefers-reduced-motion not handled: under reduce, scroll-behavior: smooth stays active (measured: 23 px covered 50 ms after a scrollTo) and the cue keeps pulsing (playState: running). Scrolling is reader-initiated and the snap animation is the browser's, but add @media (prefers-reduced-motion: reduce) { .sn-mixed-container { scroll-behavior: auto } .sdc-scroll-hint { animation: none } }. One upside of proximity mode: no content is unreachable, unlike mandatory, which can hold the reader between two points.
  • Keyboard: the container is a div without tabindex. Recent Chromium builds and Firefox make scrollable areas with no focusable child reachable from the keyboard (measured in Chromium 151: Tab stops on it, Down arrow 40 px per press with no snapping — 40, 80, 120, 160 —, PageDown 420, Space 280, End 640), Safari does not. Set tabindex="0", role="region" and an aria-label on .sn-mixed-container.
  • Contrast measured on the screenshot pixels: white h4 from 14.0:1 (section 1) to 5.1:1 (section 5) — compliant; p at 60% white: 6.0 · 5.1 · 4.2 · 3.3 · 2.8:1 — sections 3 to 5 fall below 4.5:1, and at 10.9 px the text is not “large”. Raise rgba(255, 255, 255, .6) to .85 (≥ 4.5:1 everywhere) or lighten the last gradients less. The cue (10.4 px, 30% white) measures 2.6:1 on #0a0a0f and 2.5:1 on the section it overlaps: decorative, set it aria-hidden="true" — and translate “Scrollez ↓”, which is hard-coded.
  • Sizes and structure: 13.6 px for headings, 10.9 px for paragraphs — catalog-card sizes, raise them to 14–16 px in a page. Headings are h4 with no h1h3 above: adjust the level to your outline. Sections are divs: for a screen reader the rhythm of heights does not exist, the hierarchy must also live in the headings. On touch, proximity snapping works and the 280 px container lets the gesture continue onto the page at the end of travel (default overscroll-behavior): no scroll trap.

Browser compatibility

CSS only: scroll-snap-type / scroll-snap-align (modern syntax), overflow-y: auto, linear-gradient, @keyframes; scroll-behavior: smooth as a comfort. No script, no library, no CSS variable. This is classic CSS, not an animation driven by animation-timeline: Firefox renders it fully, whereas it supports native scroll-driven animations in no version at all.

Chrome 69+✓ Full
Firefox 68+✓ Full
Safari 11+✓ Full (smooth scrolling from 15.4)
Edge 79+✓ Full
Mobile iOS 11+✓ Full (snaps on touch)
Android Chrome✓ Full (snaps on touch)

Without scroll-snap (older browsers), the container is a plain scroll box: the five sections keep their heights and backgrounds, everything stays readable, nothing snaps. Without scroll-behavior (Safari before 15.4), programmatic jumps are instant; touch and wheel scrolling do not depend on it. Without JavaScript: identical, there is none.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="demo-preview">
  <div class="sn-mixed-container">
    <div class="sn-mixed-section sn-full">
      <h4>Main section</h4>
      <p>Full height</p>
    </div>
    <div class="sn-mixed-section sn-half">
      <h4>Medium section</h4>
      <p>Half height</p>
    </div>
    <div class="sn-mixed-section sn-half">
      <h4>Medium section</h4>
      <p>Half height</p>
    </div>
    <div class="sn-mixed-section sn-quarter">
      <h4>Small</h4>
      <p>Compact</p>
    </div>
    <div class="sn-mixed-section sn-full">
      <h4>Final section</h4>
      <p>Back to full height</p>
    </div>
  </div>
  <span class="sdc-scroll-hint">Scroll ↓</span>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Snap mode (CSS — .sn-mixed-container scroll-snap-type) y (= y proximity) In proximity, snapping only acts within a third of the viewport of a point (93 px): one wheel notch from the top stays at 100 px. y mandatory always returns to the nearest point — safe here, no section exceeds the viewport.
Viewport height (CSS — .sn-mixed-container and .sn-full) 280 px / 280 px The two go together: a full section must be exactly the viewport to fill it alone, and the proximity threshold follows (one third). In a page, 100vh for both; in a component, a fixed value.
Steps (CSS — .sn-half, .sn-quarter) 140 px / 80 px These are the gaps between stops. Add classes (.sn-third at 93 px). A gap below the threshold chains two stops in one notch; a 280 gap leaves a dead zone from 94 to 186 px where a notch stops in between.
Alignment (CSS — .sn-mixed-section scroll-snap-align) start center centers a short section in the viewport with its neighbors on either side; end on the last section makes it reachable even when shorter than the viewport (its bottom aligns with the bottom of the frame).
Order and last section (HTML) full · half · half · quarter · full The last snap point must stay ≤ the travel (content − 280). End with a .sn-full, or give the last section scroll-snap-align: end, or add a padding-bottom to the container.
Backgrounds (CSS — .sn-mixed-section:nth-child(n)) 5 gradients #1e1b4b → #8b5cf6, by rank Tied to position, not to the size class: the sixth section has no background. Move them onto .sn-full / .sn-half / .sn-quarter so that color tells height.
Smoothing (CSS — scroll-behavior) smooth Animates scrollTo, anchors and keys (395 ms for 640 px measured), not the wheel or the finger. Switch to auto under prefers-reduced-motion.
Cue (HTML/CSS — .sdc-scroll-hint) “Scrollez ↓”, 8 px from the bottom, 2 s pulse Requires a position: relative parent and overlaps 10 px of the container. Raise it (bottom: 18px), translate it, or remove it together with its @keyframes.

FAQ

Because scroll-snap-type: y with no second keyword means proximity: the browser only snaps if the scroll stops within reach of a point — in Chromium, one third of the viewport, i.e. 93 px here (measured: 93 px pulled back, 94 px left alone). A wheel notch is 100 px: from the top it stops at 100 px, 100 px from point 0 and 180 px from point 280, out of reach of both. The second notch reaches 200 px, 80 px from point 280, and snapping kicks in; after that every notch lands within reach (280 → 420 → 560 → 640). For a clean stop on every notch, write scroll-snap-type: y mandatory — here no section exceeds the viewport, nothing becomes unreachable. Keeping proximity is also a choice: the reader can pause in the middle of a full section.
Vertical Snap: four 280 px sections in a 280 px viewport, y mandatory — each stop shows exactly one section, the points are all 280 px apart. Here the points are 280, 140, 140 and 80 px apart, the mode is proximity, and three stops out of five show several sections at once: height becomes a signal of weight. Snap + Progress adds a bar and navigation dots in JavaScript, but its script computes the current section with Math.round(scrollTop / clientHeight) — it assumes equal sections. Wired to these heights, it would give index 1 to stops 280 and 420 and index 2 to stops 560 and 640: two dots out of five never lit. For navigation here, compare scrollTop with the sections' actual offsetTop.
Because the container does not scroll beyond scrollHeight − clientHeight. With the last section as .sn-half, the content is 780 px, the travel 500, and that section's top is at 640: its snap point lies beyond the end (measured: stop at 500, the section stays stuck at the bottom of the frame, below the quarter). The sold code avoids the issue by ending with a .sn-full. Three ways out if your last section must be short: scroll-snap-align: end on it (its bottom aligns with the bottom of the frame — the setting used by the three examples), a padding-bottom on the container equal to the difference (140 px for a half), or an empty closing section.