
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
n-carousel
Advanced tools
A carousel component which uses the native scroll snapping functionality with enhancements and customisation. It is lightweight, accessible, and has no dependencies.
window.nCarouselInit() after updatesnpm install n-carousel
TypeScript users: Type definitions are included! No additional installation needed. See TYPESCRIPT.md for details.
<link rel="stylesheet" href="https://unpkg.com/n-carousel/n-carousel.min.css">
<script src="https://unpkg.com/n-carousel/n-carousel.min.js" type="module"></script>
Get the files from the releases page or use the minified versions:
n-carousel.min.css (~25 KB, ~4.2 KB gzipped)n-carousel.min.js (~23 KB, ~6.5 KB gzipped)<link rel="stylesheet" href="n-carousel.min.css">
<script src="n-carousel.min.js" type="module"></script>
<div class="n-carousel">
<ul class="n-carousel__content">
<li>Slide 1</li>
<li>Slide 2</li>
<li>Slide 3</li>
</ul>
<div class="n-carousel__previous">
<button><span>Previous</span></button>
</div>
<div class="n-carousel__next">
<button><span>Next</span></button>
</div>
<div class="n-carousel__index">
<button><span>1</span></button>
<button><span>2</span></button>
<button><span>3</span></button>
</div>
</div>
To avoid flashing when an initial slide is defaulted by URI hash, add the preload script at the top of your page:
<script src="n-carousel-preload.min.js"></script>
Add modifier classes to customize your carousel:
n-carousel--vertical - Vertical scrolling carouseln-carousel--rtl - Right-to-left layoutn-carousel--inline - Inline carousel (becomes overlay when clicked)n-carousel--overlay - Modal/overlay carouseln-carousel--controls-outside - Position controls outside the carouseln-carousel--auto-height - Automatically adjust height to contentn-carousel--peek - Show partial slides at edgesn-carousel--endless - Infinite loop carouseln-carousel--auto-slide - Automatically advance slidesn-carousel--instant - Instant transitions (no animation)n-carousel--tabs - Tab-style navigationn-carousel--tabs-align-end - Align tab text to end (requires --tabs)n-carousel--thumbnails - Thumbnail navigationn-carousel--lightbox - Lightbox/gallery moden-carousel--aspect - Maintain aspect ratio (for lightbox)n-carousel--index-start - Position index at startn-carousel--index-end - Position index at endn-carousel--index-align-start - Align index items to startn-carousel--index-align-center - Align index items to centern-carousel--index-align-end - Align index items to end<div class="n-carousel n-carousel--auto-height n-carousel--peek n-carousel--vertical">
<!-- content -->
</div>
Control the animation duration (in seconds):
<div class="n-carousel" data-duration="0.5">
Set the interval for auto-sliding (in seconds):
<div class="n-carousel n-carousel--auto-slide" data-interval="4">
Link directly to specific slides using URL hashes. Add an id attribute to any slide:
<div class="n-carousel">
<ul class="n-carousel__content">
<li id="slide-1">Slide 1</li>
<li id="slide-2">Slide 2</li>
<li id="slide-3">Slide 3</li>
</ul>
</div>
Then link to slides using #slide-1, #slide-2, etc. The carousel will automatically navigate to the slide when the page loads or when the hash changes.
Note: To avoid flashing when loading a page with a hash, include the preload script (see Optional Preload Script).
// Initialize all carousels in the document
window.nCarouselInit()
// Or initialize carousels within a specific container
window.nCarouselInit(containerElement)
The carousel automatically initializes on page load, but you can manually initialize dynamically added carousels using this function.
n-carousel is vanilla JS. In React, you render the HTML structure and then call window.nCarouselInit() after render (and after any slide list changes).
npm i n-carousel
main.tsx / App.tsx):import 'n-carousel/n-carousel.min.css';
import 'n-carousel/n-carousel.js'; // defines window.nCarouselInit()
import { useEffect, useRef } from 'react';
export function Gallery({ slides }) {
const hostRef = useRef(null);
useEffect(() => {
window.nCarouselInit?.(hostRef.current || document);
}, [slides.length]);
return (
<div ref={hostRef} className="n-carousel n-carousel--peek">
<ul className="n-carousel__content" style={{ '--peek': '40px' }}>
{slides.map((s) => (
<li key={s.id}>{s.title}</li>
))}
</ul>
<div className="n-carousel__previous"><button><span>Previous</span></button></div>
<div className="n-carousel__next"><button><span>Next</span></button></div>
<div className="n-carousel__index">
{slides.map((s, i) => (<button key={s.id}><span>{i + 1}</span></button>))}
</div>
</div>
);
}
This repo includes a tiny helper in react/ (used by the demo) that just calls window.nCarouselInit() for you:
import { NCarousel } from 'n-carousel-react'import 'n-carousel-react/styles'Customize the appearance using CSS variables:
.n-carousel {
--nui-control-bg: darkorchid;
--nui-control-active-bg: darkorchid;
--nui-control-highlight: darkblue;
--nui-control-color: #a5f9a5;
--nui-control-active-color: #a5f9a5;
--nui-carousel-bg: black;
--nui-carousel-color: white;
--nui-border-radius: 0; /* Border radius for controls */
}
.n-carousel__content {
--peek: 12ch; /* Peeking amount */
}
.n-carousel {
--max-height: 75vh; /* Max height for vertical carousel */
}
.n-carousel--aspect {
--ratio: 16 / 9; /* Aspect ratio for lightbox */
}
picture {
--placeholder: url(image.jpg); /* Low-res placeholder for images */
}
The --subpixel-compensation variable is automatically calculated to ensure precise scroll snapping alignment.
The Problem:
Browsers can't scroll to subpixel positions - they round to whole pixels. When element dimensions are fractional (e.g., 500.7px), scroll snapping can misalign because the browser rounds the scroll position.
The Solution:
The carousel measures the difference between the ceiling value and the actual fractional dimension, then adds that amount as padding to compensate. For example, if width is 500.3px, compensation is 0.7 (ceiling 501px minus actual 500.3px), and 0.7px padding is added to ensure perfect alignment.
Note: This is handled automatically - no manual configuration needed.
Controls can be placed outside the carousel using the data-for attribute:
<span class="n-carousel__previous" data-for="carousel-detached">
<button><span>Previous</span></button>
</span>
<span class="n-carousel__next" data-for="carousel-detached">
<button><span>Next</span></button>
</span>
<div class="n-carousel__index" data-for="carousel-detached">
<button><span>1</span></button>
<button><span>2</span></button>
<button><span>3</span></button>
</div>
<!-- ... -->
<div class="n-carousel" id="carousel-detached">
<ul class="n-carousel__content">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>
For tabs, add the class to the index controls:
<div class="n-carousel n-carousel--tabs">
<ul class="n-carousel__content">
<li>Tab 1 Content</li>
<li>Tab 2 Content</li>
<li>Tab 3 Content</li>
</ul>
<div class="n-carousel__index">
<button><span>Tab 1</span></button>
<button><span>Tab 2</span></button>
<button><span>Tab 3</span></button>
</div>
</div>
<div class="n-carousel n-carousel--lightbox n-carousel--thumbnails">
<ul class="n-carousel__content">
<li>
<figure>
<picture style="--placeholder: url(thumb.jpg)">
<img src="image.jpg" alt="Kaohsiung, Taiwan (photo)" loading="lazy" />
</picture>
<figcaption>Caption</figcaption>
</figure>
</li>
<!-- more slides -->
</ul>
<div class="n-carousel__index">
<button><img src="thumb1.jpg" alt="Slide 1 thumbnail" /></button>
<button><img src="thumb2.jpg" alt="Slide 2 thumbnail" /></button>
</div>
<div class="n-carousel__controls">
<div class="n-carousel__full-screen">
<button><span>Toggle full screen</span></button>
</div>
<div class="n-carousel__close">
<button><span>Close modal</span></button>
</div>
</div>
</div>
An inline lightbox becomes an overlay when clicking on a thumbnail:
<div class="n-carousel n-carousel--lightbox n-carousel--inline n-carousel--thumbnails">
<!-- same structure as lightbox -->
</div>
Generally targeting the last 5 years of browser versions:
The codebase uses modern JavaScript features that require:
?.) - Requires Chrome 80+, Firefox 74+, Safari 13.1+ (2020+)The specified minimum versions (Chrome 90+, Firefox 88+, Safari 14.1+) fully support all required features. The code includes Safari-specific workarounds for known scroll snap and fullscreen API quirks.
Uses native scroll snapping with a polyfill for the scrollend event where needed.
Note: Safari added native support for the scrollend event in Safari 26.2+ (2025). The polyfill (scrollyfills) is still included and will automatically be used for older Safari versions (14.1-26.1) when native support is not available. The polyfill uses feature detection ("onscrollend" in window) to determine whether to activate.
npm install
npm run build
npm run watch
# or
npm run dev
npm run serve
The project includes a comprehensive test suite with both unit and visual regression tests:
Unit Tests (Vitest):
# Run tests in watch mode
npm test
# Run tests once
npm run test:run
# Run tests with UI
npm run test:ui
# Run tests with coverage
npm run test:coverage
Visual Regression Tests (Playwright):
# Run visual tests
npm run test:visual
# Run visual tests with UI
npm run test:visual:ui
# Update visual snapshots (when visual changes are intentional)
npm run test:visual:update
Test Coverage:
Unit Tests (84 tests):
Playwright Tests (10 tests):
See tests/README.md for more details.
n-carousel/
├── n-carousel.scss # Source SCSS
├── n-carousel.js # Source JavaScript
├── n-carousel.min.css # Minified CSS
├── n-carousel.min.js # Minified JavaScript
├── n-carousel-preload.js # Preload script source (optional)
├── n-carousel-preload.min.js # Minified preload script (optional)
├── tests/ # Test files
│ ├── visual/ # Visual regression tests (Playwright)
│ └── *.test.js # Unit tests (Vitest)
├── playwright.config.js # Playwright configuration
├── vitest.config.js # Vitest configuration
└── demo/ # Demo files
MIT License - see LICENSE file for details.
Developed by Radoslav Sharapanov since 2020.
Note: This carousel uses native browser features and is designed to be lightweight and performant. For the best experience, check out the live demo to see all features in action!
FAQs
Carousel
The npm package n-carousel receives a total of 27 weekly downloads. As such, n-carousel popularity was classified as not popular.
We found that n-carousel demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.