Swiper Nested Horizontal and Vertical — Free JS Snippet

Swiper Nested Horizontal and Vertical Sliders · Carousels · Plain HTML, CSS & JS · Live preview

What's included

Features

Horizontal outer swiper containing independent vertical inner swipers
touchAngle tuned per axis to resolve diagonal gestures
Each inner swiper keeps its own position, dots and wheel handling
Per-instance pagination found relative to each element
Wheel input with forceToAxis and releaseOnEdges
Live "Row n · Item m" indicator from both swipers
Keyboard control and the a11y module on the outer swiper
Gradient artwork, no images

About this UI Snippet

Swiper Nested Horizontal and Vertical Sliders — HTML, CSS & JavaScript

Screenshot of the Swiper Nested Horizontal and Vertical Sliders snippet rendered live

Streaming apps, app stores and photo browsers all use the same layout: swipe sideways to change category, swipe vertically to move through what is inside one. Two axes of movement in a single component is a natural pattern for touch, and Swiper supports it by nesting one swiper inside another. The difficulty is not the nesting itself but making the gestures unambiguous, because a finger drag is rarely a perfect horizontal or vertical line.

The structure is one outer horizontal swiper whose slides each contain an inner vertical swiper. Because the two run on different axes, they largely stay out of each other's way — Swiper only asks for the special nested: true flag when both run the same direction — and each inner swiper is independent: it has its own position, pagination and wheel handling, which is why moving to another genre and back returns to the title you left. The inner swipers are created in a loop over each .nh-inner element, and the pagination for each is found relative to that element rather than by a shared selector, which is the usual bug when several swipers on a page all control the first one's dots.

The subtle setting is touchAngle. It is the maximum angle from the swipe axis, in degrees, at which a touch counts as a swipe for that swiper. The outer horizontal swiper uses 35 degrees, so a gesture must be quite flat to change genre; the inner vertical swiper accepts up to 55 degrees, so a slightly slanted upward flick still scrolls the titles. The two values overlap only in a narrow band, and that bias — favouring vertical browsing inside a card while making genre changes deliberate — is what stops a diagonal swipe from flicking the user to the wrong genre. Tune both numbers with a real thumb on a real phone.

On desktop, each inner swiper accepts the mouse wheel with forceToAxis so horizontal trackpad movement does not scroll it and releaseOnEdges so the wheel is not trapped at the first or last title. The position indicator in the header reads outer.activeIndex and the active inner swiper's activeIndex, refreshing when either changes. Slides use gradients and text only, and the outer pagination is styled as pills so the two sets of bullets — sideways along the bottom, vertical on the right — are visually distinct.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to preload the next row when it comes into view, remember the last position in session storage, or make the layout switch axes on desktop.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build nested Swiper sliders with Swiper 11 loaded from a CDN (bundle script and CSS).

Requirements:
- Create an outer horizontal swiper with four genre slides, each containing an inner vertical swiper with three title slides; give the container a fixed height.
- Create the inner swipers in a loop, each with its own pagination element found relative to its container, mousewheel with forceToAxis and releaseOnEdges, and touchAngle 55; set touchAngle 35 on the outer swiper.
- Show a live "Row n · Item m" indicator updated on slideChange of the outer and inner swipers.
- Style the outer pagination as pills at the bottom and the inner bullets vertically on the right; add keyboard and a11y to the outer swiper.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="nh-wrap">
  <div class="nh-top"><b>Streaming</b><span id="nhPos" aria-live="polite">Row 1 &middot; Item 1</span></div>
  <div class="swiper nh-outer" id="nhOuter">
    <div class="swiper-wrapper" id="nhRows"></div>
    <div class="swiper-pagination"></div>
  </div>
  <p class="nh-help"><strong>Swipe left / right</strong> to change genre &middot; <strong>swipe up / down</strong> inside a genre to browse its titles.</p>
</div>

Step by step

How to Use

  1. 1
    Swipe sidewaysSwipe left or right to move between genres. The header shows which row you are on.
  2. 2
    Swipe up and downSwipe vertically inside a genre to move through its titles; each has its own dots on the right.
  3. 3
    Try a diagonal swipeDrag at an angle. The touch-angle limits decide which slider takes the gesture.
  4. 4
    Come back to a genreMove away and back. Each genre remembers the title you left it on.
  5. 5
    Use the wheelOn desktop, scroll the wheel over a card to step through its titles.

Real-world uses

Common Use Cases

Streaming and content browsers
Browse categories sideways and titles vertically. For a card-deck interaction see the card-stack slider.
SHOP
Store home screens
Swipe between departments and through the featured items of each.
Stories and onboarding flows
Combine chapters with steps inside each chapter.
Learning gesture disambiguation
A hands-on look at touchAngle and nested swiper behaviour.

Got questions?

Frequently Asked Questions

When the inner and outer swipers run in the same direction. For horizontal-inside-vertical or vertical-inside-horizontal, they do not conflict and the flag is not needed.

It sets the maximum angle from the swipe axis, in degrees, for a touch to count as a swipe. Lower values require flatter gestures.

A shared selector such as ".swiper-pagination" matches the first element. Find each swiper's pagination relative to its own container.

Enable mousewheel with releaseOnEdges: true so it hands control back at the first and last slide.

Yes. Each is an independent instance, so its position is kept while the outer swiper moves elsewhere.

Keep the inner instances in an array and read outer.activeIndex and inners[outer.activeIndex].activeIndex.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Swiper, so in a framework project install it with npm install swiper (its React and Vue components take the same options) instead of the CDN tag, use the Swiper and SwiperSlide components, or create it in useEffect / onMounted / ngAfterViewInit, and release it with destroy() when the component unmounts.