npm package: @minusplusmultiply/axis
Description: A native-first React slider for lightweight, accessible horizontal carousels with container-aligned edge-to-edge layouts.
Author: Minus Plus Multiply
Latest version: 1.0.2

# Axis

Axis is a native-first React slider for lightweight horizontal carousels, built on browser scrolling and CSS scroll-snap.

It is designed for modern edge-to-edge layouts that still align cleanly to your existing page container. Set a container, gap, and gutter, and Axis handles the layout math, scrolling behaviour, and composition patterns for you.

[npm](https://www.npmjs.com/package/@minusplusmultiply/axis) · [GitHub](https://github.com/minusplusmultiply/axis) · [License](https://opensource.org/licenses/MIT)

## Contents

- [Why Axis](#why-axis)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Common Patterns](#common-patterns)
- [Hooks](#hooks)
- [Advanced Features](#advanced-features)
- [CSS-Only / No-JS](#css-only--no-js)
- [Licence](#licence)

## Documentation

- [Getting Started](./docs/getting-started.md)
- [AGENTS Guide](./docs/AGENTS.md)
- [Styling](./docs/styling.md)
- [Controls](./docs/controls.md)
- [Headless Composition](./docs/headless-composition.md)
- [State Model](./docs/state-model.md)
- [Sync](./docs/sync.md)
- [Autoplay](./docs/autoplay.md)
- [Virtualization](./docs/virtualization.md)
- [scrollBlock](./docs/scrollblock.md)
- [Accessibility](./docs/accessibility.md)
- [GSAP Integration](./docs/integrations/gsap.md)

## Why Axis

Axis is built for teams who want the flexibility of a modern slider without giving up the strengths of the platform.

Use Axis when you want:

- native touch, trackpad, wheel, and keyboard interaction
- horizontal carousels that extend edge-to-edge while staying aligned to your page container
- variable slide widths and content-driven layouts
- built-in controls or fully custom headless composition
- optional autoplay, thumbnails, virtualization, and synced sliders
- a lighter alternative to animation-heavy slider abstractions

Axis keeps native scrolling intact, then progressively enhances it with a clean React API.

## Installation

```bash
pnpm add @minusplusmultiply/axis
```

```bash
npm install @minusplusmultiply/axis
```

```bash
yarn add @minusplusmultiply/axis
```

Axis does not auto-import CSS, so import one stylesheet explicitly.

### Default compiled CSS

```tsx
import "@minusplusmultiply/axis/styles";
```

Use this in most apps. It provides the structural layout, native scroll-snap behavior, accessibility helpers, and minimal built-in control primitives.

### Vanilla compatibility import

```tsx
import "@minusplusmultiply/axis/styles/vanilla";
```

This is a compatibility import backed by the same compiled CSS asset.

### Tailwind-authored stylesheet

```tsx
import "@minusplusmultiply/axis/styles/tailwind";
```

Use this when your app compiles Tailwind-authored CSS directly.

If your app is Tailwind-first and you want stronger class conflict resolution, install `tailwind-merge` in the consuming app. Axis works without it.

The shipped stylesheets are intentionally bare-bones. They handle layout math, scrolling, snapping, and minimal built-in control structure, but they do not provide opinionated visual styling for paddles, dots, or thumbnails.

## Quick Start

```tsx
import { Axis, AxisContent, AxisControl } from "@minusplusmultiply/axis";
import "@minusplusmultiply/axis/styles";

export function FeaturedWorkSlider() {
  return (
    <Axis aria-label="Featured work" container={1200} gutter="px-6" gap={24}>
      <AxisContent>
        <article className="w-[320px] rounded-2xl bg-neutral-900 p-6 text-white">
          Project One
        </article>
        <article className="w-[460px] rounded-2xl bg-neutral-900 p-6 text-white">
          Project Two
        </article>
        <article className="w-[320px] rounded-2xl bg-neutral-900 p-6 text-white">
          Project Three
        </article>
      </AxisContent>
      <AxisControl variant="paddles" />
    </Axis>
  );
}
```

## Core Concepts

Axis is built around a simple idea:

- the slider remains a native horizontal scroller
- CSS scroll-snap handles alignment
- Axis adds layout logic, controls, state, and optional progressive enhancement

That gives you a more resilient and accessible foundation than heavily simulated drag/translate sliders, while still keeping a modern component API.

### Container-aligned edge-to-edge layouts

Axis is designed for the common layout problem where a carousel should visually run edge-to-edge, while its content still aligns to the same container as the rest of the page.

You define:

- `container` for the maximum content width
- `gutter` for the page edge spacing
- `gap` for the space between slides

Axis uses those values to calculate the correct rail offsets and spacing.

### Layout modes

```tsx
<Axis layout="full" container={1200} gutter="px-6" gap={24} />
<Axis layout="contained" container={1200} gutter="px-6" gap={24} />
```

`full` is the default and is best for edge-to-edge presentation.

`contained` keeps the viewport inside the parent bounds while still capping content width using `container`.

## Common Patterns

### Built-in controls

```tsx
<Axis aria-label="Case studies" container={1200} gutter="px-6" gap={24}>
  <AxisContent>{slides}</AxisContent>
  <AxisControl variant="paddles" />
  <AxisControl variant="dots" />
</Axis>
```

### External controls

```tsx
import { useRef } from "react";
import {
  Axis,
  AxisContent,
  AxisControl,
  type AxisControlsBinding,
} from "@minusplusmultiply/axis";

export function ExternalControlsExample() {
  const controlsRef = useRef<AxisControlsBinding | null>(null);

  return (
    <>
      <header className="flex justify-end gap-3">
        <AxisControl controls={controlsRef} variant="left" />
        <AxisControl controls={controlsRef} variant="right" />
        <AxisControl controls={controlsRef} variant="dots" />
      </header>

      <Axis aria-label="Selected projects" controlsRef={controlsRef}>
        <AxisContent>{slides}</AxisContent>
      </Axis>
    </>
  );
}
```

### Headless composition

```tsx
import {
  Axis,
  AxisPagination,
  AxisSlide,
  AxisTrack,
  AxisViewport,
  useAxisState,
} from "@minusplusmultiply/axis";

function SliderStatus() {
  const slider = useAxisState();

  return (
    <p>
      {slider.paginationIndex + 1} / {slider.slideCount}
    </p>
  );
}

export function HeadlessExample() {
  return (
    <Axis aria-label="Featured work" container={1200} gutter="px-6" gap={24}>
      <AxisViewport>
        <AxisTrack>
          <AxisSlide index={0}>Slide 1</AxisSlide>
          <AxisSlide index={1}>Slide 2</AxisSlide>
          <AxisSlide index={2}>Slide 3</AxisSlide>
        </AxisTrack>
      </AxisViewport>

      <AxisPagination />
      <SliderStatus />
    </Axis>
  );
}
```

## Hooks

Axis exposes three levels of consumer API:

- `useAxisState()` for stable reactive state
- `useAxisNavigation()` for navigation and autoplay methods
- `useAxisControls()` for the full advanced control object

For most use cases, prefer the narrower hooks.

```tsx
import {
  useAxisNavigation,
  useAxisPaginationState,
  useAxisState,
} from "@minusplusmultiply/axis";

function Toolbar() {
  const navigation = useAxisNavigation();
  const state = useAxisState();
  const pagination = useAxisPaginationState();

  return (
    <div>
      <button onClick={navigation.goToPrevious} disabled={!state.canGoPrevious}>
        Previous
      </button>
      <span>
        {pagination.paginationIndex + 1} / {pagination.slideCount}
      </span>
      <button onClick={navigation.goToNext} disabled={!state.canGoNext}>
        Next
      </button>
    </div>
  );
}
```

## Advanced Features

Axis also supports:

- autoplay
- thumbnail controls
- synced sliders
- virtualized content
- unknown-size measurement strategies
- standalone `scrollBlock`
- CSS-only / no-JS usage

See the docs for detail:

- [Getting Started](./docs/getting-started.md)
- [AGENTS Guide](./docs/AGENTS.md)
- [Styling](./docs/styling.md)
- [Controls](./docs/controls.md)
- [Headless Composition](./docs/headless-composition.md)
- [State Model](./docs/state-model.md)
- [Sync](./docs/sync.md)
- [Autoplay](./docs/autoplay.md)
- [Virtualization](./docs/virtualization.md)
- [scrollBlock](./docs/scrollblock.md)
- [Accessibility](./docs/accessibility.md)
- [GSAP Integration](./docs/integrations/gsap.md)

## CSS-Only / No-JS

If you only want the layout, spacing, native scroll behaviour, and CSS scroll-snap styling, you can use the compiled stylesheet with plain markup and no React enhancement.

```tsx
import "@minusplusmultiply/axis/styles";
```

```html
<section
  class="axis"
  data-axis-layout="full"
  data-axis-align="start"
  style="
    --axis-container-max: 1200px;
    --axis-page-gutter: 1.5rem;
    --axis-gap: 24px;
  "
>
  <div class="axis-slider">
    <div class="axis-track">
      <div class="axis-slide">
        <article style="width: 320px;">Slide 1</article>
      </div>
      <div class="axis-slide">
        <article style="width: 460px;">Slide 2</article>
      </div>
      <div class="axis-slide">
        <article style="width: 320px;">Slide 3</article>
      </div>
    </div>
  </div>
</section>
```

## Licence

## MIT

---

**-+×**  
[www.minusplusmultiply.com](https://www.minusplusmultiply.com)  
© Minus Plus Multiply. All rights reserved.