Hero Slider
Full-bleed hero carousel showing one slide at a time. Use this when each slide is a marketing message, banner, or call to action that should fill the hero area.
Hero with Images
Hero slider where each slide is a full-bleed image. Pick this when the imagery itself carries the message: photo galleries, campaign banners, or visual storytelling at the top of a page.
Deck Beta
A hero with a deck of cards beside the slides, one card per slide. The active card is at the front, the other cards fan behind it, and the front card slides back into the pile when the next one comes forward. A tap on a card goes to its slide; below the desktop breakpoint a drag across the deck follows the finger and pages on release. Below the desktop breakpoint the deck stacks above the text: the open card is centred, its two neighbours peek at the sides. Use it when every slide has its own artwork and the set is worth showing at once, as in a campaign hero.
- The cards map to the slides by order. Write them in that order and the deck places them itself, before the script runs and from init on. The first card is the open one.
- Card images fill the card, which is square unless
--deck-ratiosays otherwise. Give the first onefetchpriority="high"and the restloading="lazy". - A deck loops by default, the same way
data-swiper-loopdoes: the fan wraps, so the track wraps with it. Each loop clone carriesdata-swiper-clonewith its real slide's index, for content you update at runtime. --deck-cardis the open card's width in every layout, and--deck-ratiois its shape. Set neither and a card is a 300px square — or, in a page hero's fan layout, as tall as the hero.- Inside a page hero the fan fills the hero's height. Below the desktop breakpoint the deck stacks above the text and the hero grows to fit it; the deck does that itself, so the section needs nothing.
- Add
nds-stackedto keep the stacked layout at every width. Use the Stacked button above to see it. - The swiper fires
nds:swiper:changeon every move. Listen to it to change anything outside the swiper with the slide, such as the section background.
Responsive Multi-Slide
Display multiple slides at once with responsive breakpoints: 4 slides on large desktop, 3 on desktop, and 1 on mobile/tablet.
Peek Mode
Show part of the next slide to indicate more content. Set
--peek to the length left showing.
Loop
An endless row. The last slide is followed by the first, in both directions. Add
data-swiper-loop to the swiper.
Built-in Features
Every nds-swiper on the page initializes automatically on load with no setup code required.
Slides snap into place using CSS scroll-snap, giving smooth drag-to-scroll on desktop and natural swipe gestures on touch devices.
Three-tier slide counts (--max-slides, --mid-slides, --min-slides) adjust the visible slides at 960px and 600px breakpoints. CSS reads them, so the row is the right size before any JS runs.
Images with data-src or data-srcset load automatically as slides approach the viewport, reducing initial page weight. The image box shimmers until its source lands, so give the image a size.
The row, the arrows and the arrow keys follow the page direction. Nothing to set.
Arrow keys navigate between slides, Home jumps to the first, and End to the last. All keys are direction-aware for RTL layouts.
Set --peek to a length to reveal part of the next slide, which signals that more content is available.
Add data-swiper-loop and the row never ends. Clones at both ends make the wrap invisible. Needs more slides than the largest slides-per-view count.
Hero slides marked with hidden are revealed automatically on first visibility, deferring image decoding for off-screen slides until the carousel scrolls into view.
Access any swiper instance via element._ndsSwiper to call slideTo(), prev(), next(), or destroy().
Usage Guidelines
Best Practices
- Use the swiper for horizontally browsable collections like featured services, image galleries, or card carousels where showing everything at once would overwhelm the layout
- Use the hero variant (
nds-hero) for full-width banner sliders with background images or gradient slides at the top of a page - Use
data-srcanddata-srcsetfor lazy loading images rather than standardsrcto reduce initial page weight. Give each lazy image a width and height, or an aspect ratio, so its box can shimmer before the source lands - Do not use swiper for content that should be visible all at once. Use Grid for static card layouts or Tabs for switchable content panels
- Do not place interactive form controls inside slides. Keep slide content to display elements: text, images, cards, and links
- Add
--peek:40pxwhen the slide count exceeds the visible slots. It gives the reader a visual cue that more content is available - Use
data-swiper-looponly when the deck has more slides than the largest slides-per-view count. With fewer, the attribute is ignored - Always include
hiddenon.nds-swiper-navigation: the navigation row ships hidden and JS reveals it only when there are multiple pages. The swiper container itself does not usehidden; a CSS::afterplaceholder on.nds-swiper:not([data-nds-swiper-initialized])reserves the navigation row height before init - Keep slide heights consistent within a swiper. Mix uneven heights and the tallest slide will define the row height for all others
- Use
nds-middlefor a wide strip the reader scans rather than reads, such as a partner or logo row, where arrows beside the slides suit the shape better than a row underneath. It applies from 600px up, so addnds-centerto the navigation row as well and the mobile fallback keeps the same left-dots-right arrangement - The
nds-middlearrows sit over the left and right edges of the slide. Use it for textless slides, or keep the slide text centred so the arrows stay clear of the words. Slide text that runs to the edges will be crossed - Always include
aria-labelon navigation buttons with clear directional text like "Previous slide" and "Next slide" - For full-width section breakouts, place the swiper inside a
nds-section-body nds-max-widthcontainer so it can span beyond the content padding
Modifier Classes
| Class | Applied to | Description |
|---|---|---|
nds-hero | .nds-swiper | Full-width single-slide hero mode with overlay navigation absolutely positioned at the bottom |
nds-oncolor | .nds-swiper | Adjusts the bullets, the section title and the arrows for dark or image backgrounds. The arrows take the subtle on-color wash. A primary or neutral arrow still adds nds-oncolor on the button itself |
nds-center | .nds-swiper-navigation | Centers the bullets and pushes the prev/next buttons to the outer edges of the navigation row |
nds-middle | .nds-swiper | Moves the prev/next buttons out of the navigation row to flank the slides at their vertical centre, and hides the bullets. The swiper gains an inline gutter one button wide at each end, so the arrows sit beside the slides rather than over them. Tablet and up only (600px): below that every rule drops and the normal navigation row returns, bullets included, so pair it with nds-center on the row to choose the mobile arrangement. On nds-hero it adapts: no gutter is reserved, since the hero is full-bleed and insetting the image would be wrong, so the arrows overlay the slide at the viewport padding, and the bullets stay pinned bottom-centre. The arrows then sit over the slide edges, so use this with textless slides, or centre the slide text to keep it clear of them |
nds-deck | .nds-swiper | Deck mode. A .nds-swiper-deck of .nds-swiper-card buttons, one per slide in slide order, sits beside the track. The active card is at the front, the rest fan behind it in circular order; a tap goes to a card, a drag pages below the desktop breakpoint, and the track loops. Write the cards in slide order and nothing else: the deck places them before the script runs and the swiper owns them from init. In a hero the deck fills the hero's height at the inline-end edge; below the desktop breakpoint it stacks above the text with the two neighbours peeking, and the hero grows to fit it. See the Deck demo |
nds-stacked | .nds-swiper.nds-deck | Keeps the stacked deck layout at every width, not only below the desktop breakpoint. The deck sits above the track, the open card is centred, its two neighbours peek at the sides, and a drag pages the deck on desktop too. Use it when the fan beside the track is too wide for the space. In a hero the section grows to fit the deck at every width, not only below desktop. Toggle it in the Deck demo |
nds-lg | .nds-swiper-pagination | Large pagination bullets (16px). Default size when no modifier is applied |
nds-md | .nds-swiper-pagination | Medium pagination bullets (12px) |
nds-sm | .nds-swiper-pagination | Small pagination bullets (8px) |
nds-loading | .nds-swiper | Shows every card in the deck as a skeleton, the same bars a loading grid shows. Add it while data loads and remove it when the cards are ready |
Data Attributes
| Attribute | Description |
|---|---|
data-swiper-loop | Set on .nds-swiper for an endless row. The component clones two pages of slides at each end and jumps silently to the real slide when a scroll rests on a clone. Clones carry nds-swiper-clone, aria-hidden and inert. Ignored when the deck has no more slides than the largest slides-per-view count. Each arrow press moves one full view. When the slide count is not a multiple of the slides per view, every wrap shifts the pages by the remainder, so the bullets can skip the last one at a wrap. While looping, data-state carries no at-start or at-end token |
hidden | Place on .nds-swiper-navigation (not the container). JS re-decides visibility on every breakpoint change: removes hidden when there are multiple pages, restores it when there is only one |
data-nds-swiper-initialized | Set by JS after init completes (true). Used as a CSS hook (the pre-init ::after height reservation is scoped to :not([data-nds-swiper-initialized])) and as a guard to skip already-initialized swipers during NDS.Swiper.init(). Removed by destroy() |
data-swiper-peek | Toggled on .nds-swiper by JS when peek is active (peek > 0 and more than one page). Before JS runs, CSS reserves the peek from --peek alone. JS then counts the pages, at the loader preset and again at init. A deck left without this attribute gets a zero reserve, so a deck that fits one page shows no dead space at its end. Removed by destroy() |
data-swiper-preset | Set by the loader before the reveal on every card swiper that has not initialized yet, after it writes --slides and the peek state from --max-slides, --mid-slides, --min-slides and --peek. The skeleton row then shows the same slides per view that init will use, so init moves nothing. Removed by destroy(). |
data-swiper-single | Set by the loader with data-swiper-preset when the slides fit one page at the current breakpoint (the same page count init uses). Drops the pre-init ::after nav reservation before the reveal, because init keeps the nav hidden on a single page and the row would otherwise vanish at init. Removed by destroy(). |
CSS Custom Properties
Set these on .nds-swiper (or .nds-bullet) to override the defaults. Resolved tokens like --swiper-gap, --swiper-peek, --swiper-slides, and --swiper-total are managed by the component and should not be set directly.
| Property | Default | Description |
|---|---|---|
--max-slides | 1 | Slides per view at the large breakpoint (viewport 960px and wider). Set it inline on .nds-swiper so JS can read it too. CSS sizes the row from it the moment the main CSS applies, before any JS, in every browser |
--mid-slides | 1 | Slides per view at the medium breakpoint (600px to 959px) |
--min-slides | 1 | Slides per view at the small breakpoint (narrower than 600px) |
--total | 1 | Number of slides. Set it inline for the first paint, before the script runs — deck mode reads it to size the fan, so a deck without it draws one card wide and clips the rest. The swiper writes the real count at init, so it only matters for what the reader sees first |
--peek | unset | Length of the next slide left showing at the end of each page, for example 40px. Leave it unset, or set 0px, for no peek. JS drops the reserve when the deck fits one page |
--gap | var(--spacing-xl) | Gap between slides. Inside .nds-max-width the gutter at each end of the track is one gap too, so a neighbour ends at the edge of the track |
--padding | 0px | Gutter at each end of the track. Inside .nds-max-width the default is one gap, and the track runs to the edge of the breakout. Keep it at or below the gap. A wider gutter shows the neighbour slides in it |
--swiper-bullet-default | var(--colors-neutral-200) | Inactive pagination bullet color (light theme); shifts to neutral-700 in dark mode and to translucent white on hero/on-color backgrounds |
--swiper-bullet-default-hovered | var(--colors-neutral-300) | Inactive bullet hover color (one step from default: neutral-300 light, neutral-600 dark) |
--swiper-bullet-active | var(--colors-primary-600) | Active pagination bullet color (light theme); shifts to --colors-primary-500 in dark mode and to base white on hero/on-color backgrounds |
--swiper-bullet-active-hovered | var(--colors-primary-700) | Active bullet hover color (one step darker: --colors-primary-700 light, --colors-primary-600 dark) |
--swiper-bullet-border | transparent | Border color around the pagination bullets |
--deck-card | 300px | Deck mode: the open card's width, in every layout — the one knob that sizes a card. Its height follows from --deck-ratio. Set it inline on .nds-swiper. Inside a page hero's fan layout the default is the hero's height instead, so the card fills the hero. In the stacked layout the deck stays fluid: this is the widest the card grows to, and a narrow screen shrinks it to fit |
--deck-strip | var(--spacing-5xl) | Deck mode: width of a folded card. The fan layout only — the stacked layout has no folded cards, so it ignores this knob |
--deck-ratio | 1 | Deck mode: the shape of a card, as a plain number for width ÷ height. 1 is a square, above 1 is landscape, below 1 is portrait. --deck-card is always the card's width, so the height follows from this. Write a number, not a fraction: use 1.777, not 16/9 |
--deck-step | 0.918 | Stacked layout: how far a neighbour sits from the open card, as a share of the card's width. 1 puts their edges together, below 1 overlaps them, above 1 pushes them apart |
--deck-scale | 0.73 | Stacked layout: how large a neighbour is next to the open card, as a number from 0 to 1. 1 makes every card the same size |
The bare attributes slides-max, slides-mid, slides-min and peek still work but are deprecated. Only JS can read them, so a row authored that way keeps its old size until the loader preset runs. Use the inline properties above.
JavaScript API
All nds-swiper elements initialize automatically. Access an instance via element._ndsSwiper.