npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@minusplusmultiply/axis-slider

v1.0.13

Published

A modern, accessible, full-bleed slider component for React with CSS scroll-snap

Readme

Axis Slider

A modern, accessible, full-bleed horizontal slider designed for NextJS with native CSS scroll-snap.

npm version License: MIT GitHub Website


Features

  • Full-bleed layout — Slides extend edge-to-edge while content stays aligned to your container
  • Variable slide widths — Each slide can have its own width
  • Native scroll-snap — Smooth, GPU-accelerated scrolling with native touch/swipe
  • Keyboard navigation — Arrow keys, Home, End
  • Accessible — Full ARIA support, screen reader announcements, reduced motion support
  • SSR-safe — Works with Next.js, Remix, and other SSR frameworks
  • Flexible sizing — Use numbers, CSS variables, or Tailwind classes
  • Customizable controls — Dots, paddles, or build your own with render props
  • Imperative API — Control the slider programmatically via ref

Installation

npm install @minusplusmultiply/axis-slider
pnpm add @minusplusmultiply/axis-slider
yarn add @minusplusmultiply/axis-slider

Import Styles

With Tailwind CSS:

import "@minusplusmultiply/axis-slider/styles";

Without Tailwind (vanilla CSS):

import "@minusplusmultiply/axis-slider/styles/vanilla";

Optional: Tailwind Merge

For intelligent Tailwind class merging, install tailwind-merge:

npm install tailwind-merge

Axis Slider automatically uses it when available.


Quick Start

import { Axis, AxisSlides, AxisControl } from "@minusplusmultiply/axis-slider";
import "@minusplusmultiply/axis-slider/styles";

export default function MySlider() {
  return (
    <Axis containerMax={1200} gutter="px-6" gap={24}>
      <AxisSlides>
        <div className="w-[320px] bg-neutral-800 p-6 rounded-xl">Slide 1</div>
        <div className="w-[480px] bg-neutral-800 p-6 rounded-xl">Slide 2</div>
        <div className="w-[320px] bg-neutral-800 p-6 rounded-xl">Slide 3</div>
      </AxisSlides>
      <AxisControl variant="paddles" />
    </Axis>
  );
}

Components

<Axis>

The main container. Manages state, provides context, and handles keyboard navigation.

<Axis
  containerMax={1200} // Max content width (px, CSS var, or Tailwind class)
  gutter="px-6" // Horizontal padding (rem, CSS var, or Tailwind class)
  gap={24} // Gap between slides (px, CSS var, or Tailwind class)
  debug={false} // Show layout debug outlines
  aria-label="Image gallery"
  onSlideChange={(index) => console.log("Active:", index)}
  onScrollStart={() => console.log("Scrolling...")}
  onScrollEnd={() => console.log("Stopped")}
>
  {children}
</Axis>

<AxisSlides>

The scrollable slide container. Each direct child becomes a slide.

<AxisSlides className="py-4">
  <Card className="w-[300px]">...</Card>
  <Card className="w-[400px]">...</Card>
  <Card className="w-[300px]">...</Card>
</AxisSlides>

<AxisControl>

Navigation controls. Choose from dots, paddles, or individual buttons.

// Pagination dots
<AxisControl variant="dots" />

// Previous/Next buttons
<AxisControl variant="paddles" />

// Individual buttons (for custom positioning)
<AxisControl variant="left" className="absolute left-4 top-1/2" />
<AxisControl variant="right" className="absolute right-4 top-1/2" />

Flexible Sizing

All size props accept multiple formats:

// Raw numbers (px for containerMax/gap, rem for gutter)
<Axis containerMax={1600} gutter={2} gap={32} />

// CSS variables
<Axis containerMax="--container-max" gutter="--edge" gap="--gap" />

// Tailwind classes
<Axis containerMax="max-w-7xl" gutter="px-8" gap="gap-6" />

// Tailwind tokens
<Axis containerMax="7xl" gutter="8" gap="6" />

Imperative API

Control the slider programmatically using a ref:

import { useRef } from "react";
import { Axis, AxisSlides, type AxisRef } from "@minusplusmultiply/axis-slider";

export default function MySlider() {
  const sliderRef = useRef<AxisRef>(null);

  return (
    <>
      <button onClick={() => sliderRef.current?.goToPrevious()}>Prev</button>
      <button onClick={() => sliderRef.current?.goToNext()}>Next</button>
      <button onClick={() => sliderRef.current?.goToIndex(0)}>First</button>

      <Axis ref={sliderRef}>
        <AxisSlides>
          <div className="w-[300px]">Slide 1</div>
          <div className="w-[300px]">Slide 2</div>
          <div className="w-[300px]">Slide 3</div>
        </AxisSlides>
      </Axis>
    </>
  );
}

AxisRef Methods

| Method | Returns | Description | | ------------------ | --------- | ------------------------------ | | goToIndex(n) | void | Navigate to slide at index n | | goToNext() | void | Navigate to next slide | | goToPrevious() | void | Navigate to previous slide | | getActiveIndex() | number | Get current active slide index | | getSlideCount() | number | Get total number of slides | | isAtStart() | boolean | Check if at first slide | | isAtEnd() | boolean | Check if at last slide |


Custom Controls

Use render props for full control over navigation UI:

Custom Dots

<AxisControl
  variant="dots"
  renderDot={({ isActive, index, buttonProps }) => (
    <button {...buttonProps} className={isActive ? "bg-white" : "bg-white/30"}>
      {index + 1}
    </button>
  )}
/>

Custom Buttons

<AxisControl
  variant="paddles"
  previousContent={<ChevronLeftIcon />}
  nextContent={<ChevronRightIcon />}
  buttonClasses="p-3 rounded-full bg-black/50 hover:bg-black/70"
/>

// Or with full render control:
<AxisControl
  renderButton={({ type, isDisabled, buttonProps }) => (
    <button {...buttonProps} className="my-button">
      {type === "previous" ? <ChevronLeft /> : <ChevronRight />}
    </button>
  )}
/>

useAxisControls Hook

Build completely custom controls using the useAxisControls hook:

import { useAxisControls } from "@minusplusmultiply/axis-slider";

function CustomControls() {
  const { goToNext, goToPrevious, activeIndex, slideCount, atStart, atEnd } =
    useAxisControls();

  return (
    <div className="flex items-center gap-4">
      <button onClick={goToPrevious} disabled={atStart}>
        ←
      </button>
      <span>
        {activeIndex + 1} / {slideCount}
      </span>
      <button onClick={goToNext} disabled={atEnd}>
        →
      </button>
    </div>
  );
}

// Use inside <Axis>
<Axis>
  <AxisSlides>...</AxisSlides>
  <CustomControls />
</Axis>;

Props Reference

Axis Props

| Prop | Type | Default | Description | | --------------- | ------------------------- | ---------- | ------------------------------- | | containerMax | number \| string | 960 | Max content width | | gutter | number \| string | 1.5 | Horizontal padding (rem) | | gap | number \| string | 24 | Gap between slides (px) | | debug | boolean | false | Show debug outlines | | aria-label | string | "Slider" | Accessible label | | onSlideChange | (index: number) => void | — | Fires when active slide changes | | onScrollStart | () => void | — | Fires when scrolling starts | | onScrollEnd | () => void | — | Fires when scrolling ends |

AxisControl Props

| Prop | Type | Default | Description | | ----------------------- | ------------------------------------------ | ----------- | ----------------------------- | | variant | "dots" \| "paddles" \| "left" \| "right" | "paddles" | Control type | | className | string | — | Container classes | | dotClasses | string | — | Classes for all dots | | activeDotClasses | string | — | Classes for active dot | | inactiveDotClasses | string | — | Classes for inactive dots | | buttonClasses | string | — | Classes for all buttons | | disabledButtonClasses | string | — | Classes for disabled buttons | | previousButtonClasses | string | — | Classes for previous button | | nextButtonClasses | string | — | Classes for next button | | previousContent | ReactNode | "←" | Previous button content | | nextContent | ReactNode | "→" | Next button content | | renderDot | function | — | Custom dot render function | | renderButton | function | — | Custom button render function |


Accessibility

Axis Slider is built with accessibility in mind:

  • Keyboard navigation — Arrow keys, Home, End
  • ARIA attributes — Proper roles, labels, and live regions
  • Screen reader support — Announces slide changes
  • Focus management — Logical focus order
  • Reduced motion — Respects prefers-reduced-motion

Browser Support

  • Chrome, Firefox, Safari, Edge (modern versions)
  • CSS scroll-snap (95%+ browser support)
  • Graceful degradation — works as a scrollable container without JavaScript
  • SSR-safe — compatible with Next.js, Remix, Astro, etc.

Performance

  • Native scroll-snap — GPU-accelerated, 60fps scrolling
  • Passive event listeners — No scroll jank
  • Minimal footprint — ~20KB minified, no dependencies
  • No animation libraries — Pure CSS transitions
  • Native touch support — Swipe works out of the box

License

MIT © Minus Plus Multiply


Links