# Introduction
MotionRail is a lightweight, smooth carousel library with momentum-based scrolling, snap alignment, and responsive breakpoints. It works seamlessly with vanilla JavaScript or your favorite framework.
## Features
- ๐ฏ๓ ๓ ๓ ข๓ ๓ ฆ๓ ๓ ๓ ๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ค๓ ๓ ๓ ๓ ฃ๓ ฃ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ค๓ ข๓ ๓ **Momentum-based scrolling** - Natural drag physics with smooth animations
- ๐ฑ๓ ต๓ ๓ ๓ ๓ ข๓ ค๓ ๓ ๓ ฃ๓ ฃ๓ ๓ ข๓ ๓ ฃ๓ ๓ ๓ ๓ ฃ๓ ๓ ฆ๓ ๓ ๓ ๓ ๓ ฉ๓ ๓ ฅ๓ ค๓ ฃ **Responsive breakpoints** - Configure columns and gaps for different screen sizes
- โฟ๓ ๓ ข๓ ๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ค๓ ๓ ๓ ๓ ๓ ง๓ ๓ ฉ๓ ฃ๓ ๓ ๓ ๓ ข๓ ๓ ๓ ๓ ค **Snap alignment** - Automatic snap-to-item positioning
- ๐๓ ฑ๓ ฅ๓ ค๓ ๓ ๓ ๓ ๓ ฉ๓ ๓ ค๓ ๓ ๓ ค๓ ๓ ๓ ๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ค๓ ฅ๓ ข๓ ๓ **Autoplay support** - Optional auto-scrolling with pause on interaction
- โ๏ธ๓ ๓ ๓ ผ๓ ๓ ฃ๓ ฅ๓ ๓ ๓ ๓ ข๓ ค๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ข๓ ๓ ๓ ๓ ฃ๓ ๓ ฃ **RTL support** - Built-in right-to-left layout support
- ๐จ๓ ฝ๓ ๓ ๓ ๓ ข๓ ๓ ๓ ณ๓ ๓ ๓ ๓ ท๓ ข๓ ๓ ๓ ๓ ๓ ๓ ฃ๓ ค๓ ๓ ข๓ ฉ **CSS Grid based** - Modern layout with customizable styling
- ๐ชถ๓ ฝ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ฅ๓ ๓ ค๓ ข๓ ๓ ๓ ๓ ๓ ๓ ๓ ค๓ ง๓ ๓ ๓ ๓ ๓ ค **Lightweight** - Zero dependencies, minimal bundle size
- ๐ฎ๓ ๓ ๓ ค๓ ๓ ๓ ๓ ฑ๓ ๓ น๓ ๓ ๓ ๓ ๓ ค๓ ข๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ๓ ค๓ ฃ **Full control API** - Programmatic navigation and playback control
- ๐งฉ๓ ต๓ จ๓ ค๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ฃ๓ ๓ ๓ ๓ ข๓ ๓ ๓ ๓ ๓ ๓ ๓ ฃ๓ ฃ๓ ๓ ๓ ๓ ฃ๓ ฃ๓ ๓ ๓ ๓ ๓ ๓ ค๓ ๓ ๓ ฃ **Extension system** - Modular architecture with built-in extensions
- โก๓ ๓ ๓ ๓ ๓ ๓ ๓ ฃ๓ ฃ๓ ๓ ๓ ข๓ ๓ ๓ ๓ ง๓ ๓ ข๓ ๓ ๓ ๓ ๓ ค๓ ๓ ๓ ข๓ ๓ ค๓ ๓ ๓ **Framework integrations** - React, Preact, Qwik, Solid, Vue, Svelte components
## Why MotionRail?
MotionRail was built to provide a modern, performant carousel experience without the bloat. It uses native CSS Grid for layout, modern JavaScript APIs, and provides first-class framework integrations.
### Key Differences
- **Lightweight**: No dependencies, minimal bundle size
- **Modern**: Built with modern web standards (CSS Grid, Intersection Observer, etc.)
- **Framework-first**: Not just wrappers - true framework integrations
- **Extensible**: Modular extension system for adding features
- **Accessible**: Keyboard navigation and semantic HTML
## Browser Support
MotionRail works in all modern browsers:
- Chrome (142+)
- Edge (142+)
- Firefox (145+)
- Safari (18.6+)
- Samsung Internet (28+)
- Opera (124+)
- Modern mobile browsers
Requires support for:
- CSS Grid
- Pointer Events API
- Container Queries
- Scroll Snap
- IntersectionObserver API
- ResizeObserver API
## Next Steps
- [Installation](/docs/installation) - Get started with MotionRail
- [Quick Start](/docs/quick-start) - Learn the basics
- [Framework Integrations](/docs/frameworks/react) - Use with your favorite framework
---
# Installation
## Package Manager
Install MotionRail using your preferred package manager:
::: code-group
```bash [npm]
npm install motionrail
```
```bash [pnpm]
pnpm add motionrail
```
```bash [yarn]
yarn add motionrail
```
```bash [bun]
bun add motionrail
```
:::
## CDN
You can also use MotionRail via CDN:
```html
```
## Next Steps
- [Quick Start](/docs/quick-start) - Get started with a basic example
- [Configuration](/docs/configuration) - Learn about all available options
- [Framework Integrations](/docs/frameworks/react) - Use with your framework
---
# Quick Start
## Basic Setup
### 1. HTML Structure
Create the carousel HTML structure with the required data attributes:
```html
Item 1
Item 2
Item 3
Item 4
Item 5
```
::: tip ABOUT HEIGHT
**The carousel height is not set by default**, giving you full control over your layout. You can set the height on individual items, or use any CSS approach that fits your design.
:::
**Structure layers:**
```
[data-motionrail] - Wrapper element
โโโ [data-motionrail-scrollable] - Scrollable container with overflow and snap behavior
โโโ [data-motionrail-grid] - Grid layout container
โโโ Direct children - Carousel items
```
### 2. Import Styles & Initialize the Carousel
```js
import { MotionRail } from "motionrail";
import "motionrail/style.css";
const element = document.getElementById("carousel");
const carousel = new MotionRail(element, {
// set autoplay true for this example
autoplay: true,
// set the breakpoints
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 400, columns: 2, gap: "16px" },
{ width: 550, columns: 3, gap: "20px" },
],
});
```
### Result
**Try it yourself:** Drag the left or right edge to resize the container and see how the carousel responds to different breakpoints.
## Using UMD (CDN)
If you prefer not to use a build tool, you can use the UMD version via CDN:
```html
Item 1
Item 2
Item 3
Item 4
Item 5
```
## Next Steps
- [Configuration](/docs/configuration) - Learn about all available options
- [Breakpoints](/docs/breakpoints) - Understand responsive configuration
- [API Methods](/docs/api) - Control the carousel programmatically
- [Extensions](/docs/extensions/) - Add arrows, dots, and more
---
# Configuration
## Options Overview
```ts
interface MotionRailOptions {
autoplay?: boolean;
rtl?: boolean;
delay?: number;
resumeDelay?: number;
breakpoints?: MotionRailBreakpoint[];
onChange?: (state: MotionRailState) => void;
extensions?: MotionRailExtension[];
containerName?: string;
}
```
See [MotionRailOptions](/docs/api/types/motionrail-options) for complete type documentation.
## Core Options
### `autoplay`
- **Type**: `boolean`
- **Default**: `false`
Enable automatic scrolling through carousel items.
```js
const carousel = new MotionRail(element, {
autoplay: true,
});
```
### `rtl`
- **Type**: `boolean`
- **Default**: `false`
Enable right-to-left layout support for RTL languages.
See [RTL Support](/docs/rtl) for detailed documentation and examples.
```js
const carousel = new MotionRail(element, {
rtl: true,
});
```
### `delay`
- **Type**: `number`
- **Default**: `3000`
Delay between auto-scrolls in milliseconds. Only applies when `autoplay` is enabled.
```js
const carousel = new MotionRail(element, {
autoplay: true,
delay: 2500, // Auto-scroll every 2.5 seconds
});
```
### `resumeDelay`
- **Type**: `number`
- **Default**: `4000`
Time to wait before resuming autoplay after user interaction (in milliseconds).
```js
const carousel = new MotionRail(element, {
autoplay: true,
resumeDelay: 5000, // Resume after 5 seconds of inactivity
});
```
### `breakpoints`
- **Type**: [MotionRailBreakpoint](/docs/api/types/motionrail-breakpoint)[]
- **Default**: `[{ columns: 1, gap: "0px" }]`
Array of responsive breakpoint configurations. See [Breakpoints](/docs/breakpoints) for detailed documentation.
```js
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" }, // Mobile
{ width: 768, columns: 2, gap: "16px" }, // Tablet
{ width: 1024, columns: 3, gap: "20px" }, // Desktop
],
});
```
## Advanced Options
### `onChange`
- **Type**: `(state: MotionRailState) => void`
- **Default**: `undefined`
Callback function fired when carousel state changes (on scroll, resize, etc.). Receives [MotionRailState](/docs/api/types/motionrail-state) object.
```js
const carousel = new MotionRail(element, {
onChange: (state) => {
console.log("Visible items:", state.visibleItemIndexes);
console.log("At start:", state.isFirstItemVisible);
console.log("At end:", state.isLastItemVisible);
},
});
```
**State object:**
```ts
interface MotionRailState {
totalItems: number; // Total number of items in carousel
visibleItemIndexes: number[]; // Array of currently visible item indexes
isFirstItemVisible: boolean; // Whether the first item is visible
isLastItemVisible: boolean; // Whether the last item is visible
}
```
See [MotionRailState](/docs/api/types/motionrail-state) for complete type documentation.
### `containerName`
Unique CSS container name for scoping container queries and styles to a specific carousel instance. This prevents style leakage and enables FOUC-free (Flash of Unstyled Content) rendering, especially with SSR. If not provided, a random name is generated automatically.
- **Type**: `string`
- **Default**: Randomly generated
```js
const { containerName, containerQueries } = MotionRail.getBreakPoints({
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
{ width: 1024, columns: 3, gap: "32px" },
],
totalItems: 8,
containerName: "my-carousel-container",
});
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
{ width: 1024, columns: 3, gap: "32px" },
],
containerName: "my-carousel-container",
});
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { Arrows } from "motionrail/extensions/arrows";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
// FOUC prevention: generate containerName and containerQueries for SSR
const { containerName, containerQueries } = MotionRail.getBreakPoints({
breakpoints: [
{ columns: 1, gap: "12px" },
{ width: 480, columns: 2, gap: "16px" },
{ width: 768, columns: 3, gap: "20px" },
{ width: 1024, columns: 4, gap: "24px" },
],
totalItems: 8,
containerName: "my-carousel-container",
});
// Inject containerQueries into for SSR/FOUC-free styling
// for true FOUC prevention, use SSR to inject:
//
// See the Framework Integrations guide for real SSR examples
const carousel = new MotionRail(document.getElementById("carousel"), {
autoplay: true,
rtl: false,
delay: 3000,
resumeDelay: 4000,
breakpoints: [
{ columns: 1, gap: "12px" },
{ width: 480, columns: 2, gap: "16px" },
{ width: 768, columns: 3, gap: "20px" },
{ width: 1024, columns: 4, gap: "24px" },
],
containerName,
onChange: (state) => {
console.log("State updated:", state);
},
extensions: [Arrows({ loop: true })],
});
```
## Next Steps
- [Breakpoints](/docs/breakpoints) - Detailed breakpoint configuration
- [API Methods](/docs/api) - Control the carousel programmatically
- [Extensions](/docs/extensions/) - Add more functionality
---
# SSR & FOUC-Free Styling
MotionRail provides a robust approach to ensure your carousels render correctly and instantly styled, both during server-side rendering (SSR) and on the client. This prevents the common web issue known as FOUC (Flash of Unstyled Content), where users briefly see unstyled or broken layouts before CSS is applied.
## Why FOUC Happens
FOUC occurs when the browser displays content before the necessary styles are loaded or applied. In SSR and modern JavaScript frameworks, this can be especially problematic for dynamic layouts like carousels, where grid columns, gaps, and responsive breakpoints are critical for correct appearance.
## MotionRailโs Solution: getBreakPoints
MotionRail exposes a static utility, `getBreakPoints`, which generates both a unique container name and the required container query CSS for your carouselโs breakpoints. By calling this method during SSR (or at the top of your component), you can inject the exact CSS needed for your carouselโs layout before the page is rendered.
**Usage:**
```js
import { MotionRail } from "motionrail";
const { containerName, containerQueries } = MotionRail.getBreakPoints({
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
],
totalItems: 8,
});
```
- `breakpoints`: Array of breakpoint objects (columns, gap, width)
- `totalItems`: Number of items in the carousel
- `containerName` (optional): Provide your own, or let MotionRail generate one
**In your component render:**
```jsx
{/* ...carousel items... */}
```
## Benefits
- **No FOUC:** Styles are present on first render, so users never see an unstyled carousel.
- **SSR-Ready:** Works seamlessly with server-rendered frameworks (Next.js, Nuxt, etc.).
- **Scoped Styles:** Each carousel instance gets a unique container name, preventing style collisions.
- **Responsive:** Container queries ensure correct columns and gaps at every breakpoint.
## When to Use
- Any time you use MotionRail in an SSR context.
- When you want to guarantee a polished, flicker-free user experience.
---
# Breakpoints
Breakpoints allow you to configure how many columns and what gap size to use at different **container widths**, creating a fully responsive carousel.
## Configuration
```ts
interface MotionRailBreakpoint {
width?: number; // Maximum viewport width (optional)
columns?: number; // Number of visible columns (optional)
gap?: string; // Gap between items (optional, CSS value)
}
```
See [MotionRailBreakpoint](/docs/api/types/motionrail-breakpoint) for complete type documentation.
## How Breakpoints Work
Breakpoints are applied **mobile-first**. Start with your default configuration (no `width` property), then add breakpoints with increasing `width` values.
::: tip ELEMENT WIDTH, NOT SCREEN WIDTH
The `width` values in breakpoints refer to the **carousel container's width**, not the viewport/screen width. MotionRail uses CSS Container Queries internally, making carousels responsive to their parent container rather than the screen size.
:::
::: warning
The examples below assume the carousel container is full-width. If your carousel is constrained within a narrower container, adjust the breakpoint values accordingly.
:::
### Basic Example
```js
// the following is an example for full-width carousel
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" }, // Default (mobile)
{ width: 768, columns: 2, gap: "16px" }, // Tablets
{ width: 1024, columns: 3, gap: "20px" }, // Desktop
],
});
```
**How it applies:**
- 0-767px: 1 column, 16px gap
- 768-1023px: 2 columns, 16px gap
- 1024px+: 3 columns, 20px gap
## Common Patterns
### Variable Columns (1 โ 2 โ 3 โ 4)
```js
breakpoints: [
{ columns: 1, gap: "12px" }, // < 480px
{ width: 480, columns: 2, gap: "16px" }, // 480px - 767px
{ width: 768, columns: 3, gap: "20px" }, // 768px - 1023px
{ width: 1024, columns: 4, gap: "24px" }, // >= 1024px
];
```
### Fixed Layout (No Responsiveness)
```js
breakpoints: [
{ columns: 2, gap: "20px" }, // Always 2 columns at any width
];
```
### Mobile-First Progressive Enhancement
```js
breakpoints: [
{ columns: 1, gap: "8px" }, // Small phones
{ width: 375, columns: 1, gap: "12px" }, // Standard phones
{ width: 640, columns: 2, gap: "16px" }, // Phablets/small tablets
{ width: 768, columns: 2, gap: "20px" }, // Tablets
{ width: 1024, columns: 3, gap: "24px" }, // Small desktops
{ width: 1280, columns: 4, gap: "28px" }, // Standard desktops
{ width: 1536, columns: 5, gap: "32px" }, // Large screens
];
```
### Columns Only (Consistent Gap)
```js
breakpoints: [
{ columns: 1 },
{ width: 768, columns: 2 },
{ width: 1024, columns: 3 },
];
// Gap defaults to "0px" for all breakpoints
```
### Gap Only (Single Column)
```js
breakpoints: [
{ columns: 1, gap: "12px" },
{ width: 768, gap: "20px" },
{ width: 1024, gap: "28px" },
];
// Always 1 column, but gap increases on larger screens
```
## Default Behavior
If you omit the `breakpoints` option entirely, MotionRail uses this default:
```js
breakpoints: [{ columns: 1, gap: "0px" }];
```
## Important Notes
### Width Ordering
::: warning
Breakpoints **must** be ordered from smallest to largest width. The first breakpoint should have no `width` property.
:::
**โ Wrong:**
```js
breakpoints: [
{ width: 1024, columns: 3, gap: "20px" }, // This won't work as intended
{ width: 768, columns: 2, gap: "16px" },
{ columns: 1, gap: "16px" },
];
```
**โ Correct:**
```js
breakpoints: [
{ columns: 1, gap: "16px" }, // Default (no width)
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
];
```
### Container Queries
MotionRail uses **CSS Container Queries** internally, which means breakpoints respond to the carousel container's width, not the viewport width. This allows carousels to be responsive within their parent container.
### Gap Values
The `gap` property accepts any valid CSS value:
```js
breakpoints: [
{ columns: 2, gap: "16px" }, // Pixels
{ width: 768, columns: 2, gap: "1rem" }, // Rem units
{ width: 1024, columns: 2, gap: "2%" }, // Percentage
{ width: 1280, columns: 2, gap: "1.5em" }, // Em units
];
```
## Dynamic Updates
Changes to breakpoints require recreating the carousel instance:
```js
let carousel = new MotionRail(element, {
breakpoints: [{ columns: 2, gap: "16px" }],
});
// To change breakpoints:
carousel.destroy();
carousel = new MotionRail(element, {
breakpoints: [{ columns: 3, gap: "20px" }],
});
```
## Examples
### E-commerce Product Grid
```js
breakpoints: [
{ columns: 2, gap: "12px" }, // Mobile: 2 products
{ width: 640, columns: 3, gap: "16px" }, // Small tablets: 3 products
{ width: 1024, columns: 4, gap: "20px" }, // Desktop: 4 products
{ width: 1280, columns: 5, gap: "24px" }, // Large screens: 5 products
];
```
### Image Gallery
```js
breakpoints: [
{ columns: 1, gap: "0px" }, // Mobile: full-width images
{ width: 768, columns: 2, gap: "8px" }, // Tablets: 2 images
{ width: 1024, columns: 3, gap: "12px" }, // Desktop: 3 images
];
```
### Content Carousel
```js
breakpoints: [
{ columns: 1, gap: "20px" }, // Mobile: one card at a time
{ width: 1024, columns: 2, gap: "32px" }, // Desktop: two cards
];
```
## Next Steps
- [Configuration](/docs/configuration) - All configuration options
- [API Methods](/docs/api) - Programmatic control
- [Examples](#) - More real-world examples
---
# RTL (Right-to-Left) Support
MotionRail provides built-in support for right-to-left languages such as Arabic, Hebrew, and other RTL languages.
## Configuration
Enable RTL mode by setting the `rtl` option to `true`:
```ts
const carousel = new MotionRail(container, {
rtl: true,
});
```
## How RTL Works
When RTL mode is enabled:
- The carousel scrolls from right to left instead of left to right
- Navigation arrows are reversed (right arrow goes to previous, left arrow goes to next)
- Thumbnails and dots order is reversed
- All directional logic is automatically adjusted
## Example
Here's a complete example using Vue with RTL enabled:
### Live Demo
### Code (Simplified)
```vue {7,14}
1
2
3
```
::: tip HTML DIR ATTRIBUTE
Don't forget to add the `dir="rtl"` attribute to the container element (or a parent element) to properly set the text direction for RTL languages.
:::
## Framework Examples
### React
```tsx {7,14}
import { MotionRail } from "motionrail/react";
import { Arrows } from "motionrail/extensions/arrows";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
const options = {
rtl: true,
extensions: [Arrows()],
};
function RTLCarousel() {
return (
);
}
```
## SSR Support (SolidStart)
When using SSR-enabled frameworks like **SolidStart**, you must use `clientOnly` to prevent server-side rendering issues since MotionRail relies on browser APIs:
```tsx
import { clientOnly } from "@solidjs/start";
import "motionrail/style.css";
const MotionRail = clientOnly(() =>
import("motionrail/solid").then((m) => ({ default: m.MotionRail })),
);
const options = {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
],
};
export default function Home() {
return (
Item 1
Item 2
Item 3
);
}
```
## Dynamic Children
The Solid component automatically calls `update()` when children change:
```jsx
import { createSignal, For } from "solid-js";
import { MotionRail } from "motionrail/solid";
// Define options outside to prevent re-renders
const options = { breakpoints: [{ columns: 3, gap: "20px" }] };
function DynamicCarousel() {
const [items, setItems] = createSignal(["Item 1", "Item 2", "Item 3"]);
const addItem = () => {
setItems([...items(), `Item ${items().length + 1}`]);
};
const removeItem = () => {
setItems(items().slice(0, -1));
};
return (
<>
{(item) =>
{item}
}
>
);
}
```
## TypeScript
Full TypeScript support is included:
```tsx
import { createSignal } from "solid-js";
import { MotionRail } from "motionrail/solid";
import type { MotionRailOptions, MotionRailState } from "motionrail";
function TypedCarousel() {
let carousel:
| InstanceType
| undefined;
const options: MotionRailOptions = {
autoplay: true,
breakpoints: [{ columns: 3, gap: "20px" }],
onChange: (state: MotionRailState) => {
console.log(state);
},
};
return (
(carousel = instance)} options={options}>
Item 1
Item 2
);
}
```
## Bundle Size
**Solid.js integration**: 0.89 kB (gzipped: 0.48 kB)
## Next Steps
- [Configuration](/docs/configuration) - All configuration options
- [API Methods](/docs/api) - Programmatic control
- [Extensions](/docs/extensions/) - Add more functionality
---
# Vue Integration
MotionRail provides a first-class Vue 3 component (SFC) with full TypeScript support.
## FOUC-Free Styling Example
```vue
```
## Basic Usage
```vue
Item 1
Item 2
Item 3
```
## Props
### `options`
- **Type**: [MotionRailOptions](/docs/api/types/motionrail-options)
- **Required**: No
- **Default**: `{}`
Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options.
```vue
```
## Template Ref Access
Use template refs to access the MotionRail instance and container:
```vue
Item 1
Item 2
```
### Exposed Properties
The component exposes two properties via `defineExpose`:
- **`instance`**: The MotionRail class instance (for API methods)
- **`container`**: The container HTMLDivElement
```vue
```
## Complete Example
```vue
```
## Dynamic Children
The Vue component automatically calls `update()` when slot content changes:
```vue
{{ item }}
```
## TypeScript
Full TypeScript support with `
Item 1
Item 2
```
## Attributes
All attributes are passed to the root `div` element:
```vue
```
## Bundle Size
**Vue integration**: 1.22 kB (gzipped: 0.62 kB)
## Next Steps
- [Configuration](/docs/configuration) - All configuration options
- [API Methods](/docs/api) - Programmatic control
- [Extensions](/docs/extensions/) - Add more functionality
---
# Svelte Integration
MotionRail provides a Svelte component compatible with both Svelte 4 and Svelte 5 (classic API).
## FOUC-Free Styling Example
```svelte
{@html ``}
{#each [1,2,3,4,5,6,7,8] as i}
{/* ...carousel item content... */}
{/each}
```
## Basic Usage
```svelte
Item 1
Item 2
Item 3
```
## Props
### `options`
- **Type**: [MotionRailOptions](/docs/api/types/motionrail-options)
- **Required**: No
- **Default**: `{}`
Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options.
```svelte
```
### `bind:instance`
- **Type**: `MotionRailClass | null`
Bind to get the MotionRail instance for programmatic control.
```svelte
Item 1
Item 2
```
### `bind:container`
- **Type**: `HTMLDivElement | undefined`
Bind to get the container HTMLDivElement.
```svelte
Item 1
Item 2
```
### Other Props
All other props are passed to the root `div` element:
```svelte
```
## Complete Example
```svelte
```
## Dynamic Children
The Svelte component automatically calls `update()` when slot content changes using a MutationObserver:
```svelte
{#each items as item}
{item}
{/each}
```
## TypeScript
Full TypeScript support with `
Item 1
Item 2
```
## Exported Functions
The component exports two helper functions:
### `getInstance()`
Returns the MotionRail instance.
```svelte
```
### `getContainer()`
Returns the container HTMLDivElement.
```svelte
```
## Bundle Size
**Svelte integration**: 2.21 kB (gzipped: 0.94 kB)
## Next Steps
- [Configuration](/docs/configuration) - All configuration options
- [API Methods](/docs/api) - Programmatic control
- [Extensions](/docs/extensions/) - Add more functionality
---
# Qwik Integration
MotionRail provides a first-class Qwik component with full TypeScript support and optimized for Qwik's resumability.
## FOUC-Free Styling Example
```tsx
import { component$ } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
import { MotionRail as MotionRailClass } from "motionrail";
import "motionrail/style.css";
export default component$(() => {
// FOUC-safe container query setup for the first carousel
const { containerName, containerQueries } = MotionRailClass.getBreakPoints({
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
],
totalItems: 8,
});
return (
{/* FOUC prevention: inject containerQueries in a
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
);
});
```
## Props
### `options`
- **Type**: [MotionRailOptions](/docs/api/types/motionrail-options)
- **Required**: No
Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options.
::: warning Important: noSerialize Required
When passing options that include **functions** (like `onChange`) or **extension instances**, you must wrap the options object with `noSerialize()` from Qwik to prevent serialization errors.
:::
```tsx
import { component$, noSerialize } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
import { Arrows } from "motionrail/extensions/arrows";
export default component$(() => {
return (
console.log(state),
})}
>
{/* items */}
);
});
```
### Other Props
All other props are passed to the root `div` element:
```tsx
{/* items */}
```
## Complete Example
```tsx
import { component$, useSignal, noSerialize } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
import { Arrows } from "motionrail/extensions/arrows";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
export default component$(() => {
const currentState = useSignal(null);
return (
);
});
```
## Dynamic Children
The Qwik component automatically updates when slot content changes:
```tsx
import { component$, useSignal, noSerialize } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
export default component$(() => {
const items = useSignal([1, 2, 3]);
return (
{items.value.map((item) => (
Item {item}
))}
);
});
```
## TypeScript
Full TypeScript support is included:
```tsx
import { component$, useSignal, noSerialize } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
import type { MotionRailOptions, MotionRailState } from "motionrail";
export default component$(() => {
const currentState = useSignal(null);
const options: MotionRailOptions = {
autoplay: true,
breakpoints: [{ columns: 3, gap: "20px" }],
onChange: (state: MotionRailState) => {
currentState.value = state;
},
};
return (
Item 1
Item 2
);
});
```
## Working with Extensions
When using extensions, always wrap the options object with `noSerialize()`:
```tsx
import { component$, noSerialize } from "@builder.io/qwik";
import { MotionRail } from "motionrail/qwik";
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
import "motionrail/extensions/dots/style.css";
export default component$(() => {
return (
Item 1
Item 2
Item 3
);
});
```
## Bundle Size
**Qwik integration**: 0.95 kB (gzipped: 0.52 kB)
## Next Steps
- [Configuration](/docs/configuration) - All configuration options
- [API Methods](/docs/api) - Programmatic control
- [Extensions](/docs/extensions/) - Add more functionality
---
# Extensions
MotionRail supports a modular extension system that allows you to add functionality without bloating the core library.
## Built-in Extensions
MotionRail comes with several pre-built extensions:
- **[Arrows](/docs/extensions/arrows)** - Previous/next navigation arrows
- **[Dots](/docs/extensions/dots)** - Pagination dots indicator
- **[Thumbnails](/docs/extensions/thumbnails)** - Thumbnail navigation
- **[Logger](/docs/extensions/logger)** - Debug logging for development
## Installation
Extensions are included with MotionRail but must be imported separately:
```js
import { MotionRail } from "motionrail";
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
// Don't forget to import extension styles
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
import "motionrail/extensions/dots/style.css";
```
## Using Extensions
Add extensions through the `extensions` option:
```js
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
],
extensions: [Arrows({ loop: true }), Dots({ showIndex: true })],
});
```
## Extension API
All extensions follow a consistent lifecycle API:
```ts
interface MotionRailExtension {
name: string;
onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
}
```
See [MotionRailExtension](/docs/api/types/motionrail-extension) for complete type documentation.
### Lifecycle Hooks
#### `onInit(motionRail, state)`
Called once when the carousel initializes. Use this to set up DOM elements, event listeners, or initial state.
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Current [MotionRailState](/docs/api/types/motionrail-state)
#### `onUpdate(motionRail, state)`
Called whenever the carousel state changes (scroll, resize, content update).
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Updated [MotionRailState](/docs/api/types/motionrail-state)
#### `onDestroy(motionRail, state)`
Called when the carousel is destroyed. Use this to clean up DOM elements, event listeners, and timers.
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Final [MotionRailState](/docs/api/types/motionrail-state)
## State Object
All hooks receive the current state:
```ts
interface MotionRailState {
totalItems: number; // Total number of items
visibleItemIndexes: number[]; // Currently visible item indexes
isFirstItemVisible: boolean; // First item is visible
isLastItemVisible: boolean; // Last item is visible
}
```
See [MotionRailState](/docs/api/types/motionrail-state) for complete type documentation.
## Multiple Extensions
You can use multiple extensions together:
```js
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
import { Thumbnails } from "motionrail/extensions/thumbnails";
const carousel = new MotionRail(element, {
extensions: [
Arrows({ loop: false }),
Dots({ showIndex: true }),
Thumbnails({ thumbnailWidth: 80 }),
],
});
```
## Next Steps
- [Arrows Extension](/docs/extensions/arrows) - Navigation arrows
- [Dots Extension](/docs/extensions/dots) - Pagination indicators
- [Thumbnails Extension](/docs/extensions/thumbnails) - Thumbnail navigation
- [Logger Extension](/docs/extensions/logger) - Debug logging
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own
---
# Arrows Extension
Add previous/next navigation arrows to your carousel.
## Usage
```js
import { MotionRail } from "motionrail";
import { Arrows } from "motionrail/extensions/arrows";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
const carousel = new MotionRail(element, {
extensions: [Arrows()],
});
```
## Options
### `loop`
- **Type**: `boolean`
- **Default**: `true`
When `false`, arrows are disabled at carousel edges (first/last items visible).
```js
Arrows({ loop: false });
```
### `leftIcon`
- **Type**: `string`
- **Default**: Default SVG left arrow
Custom HTML string for the left arrow icon.
```js
Arrows({
leftIcon: "",
});
```
### `rightIcon`
- **Type**: `string`
- **Default**: Default SVG right arrow
Custom HTML string for the right arrow icon.
```js
Arrows({
rightIcon: "",
});
```
### `log`
- **Type**: `boolean`
- **Default**: `false`
Enable console logging for debugging.
```js
Arrows({ log: true });
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { Arrows } from "motionrail/extensions/arrows";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
],
extensions: [
Arrows({
loop: true,
leftIcon: "โ",
rightIcon: "โ",
}),
],
});
```
### UMD (CDN)
```html
```
## Features
- **Auto-hide**: Arrows automatically hide when all items are visible
- **RTL-aware**: Navigation direction swaps in RTL mode
- **Customizable**: Use SVG, text, or HTML for icons
- **Disabled state**: Visual feedback when `loop: false` and at edges
- **Accessible**: Proper ARIA labels and keyboard support
## Styling
View the default styles: [/src/extensions/arrows/style.css](https://github.com/juji/motionrail/blob/main/src/extensions/arrows/style.css)
**Custom Icons:**
Use text symbols:
```js
Arrows({ leftIcon: "โน", rightIcon: "โบ" });
```
Or SVG icons:
```js
Arrows({
leftIcon: '',
rightIcon: '',
});
```
## Next Steps
- [Dots Extension](/docs/extensions/dots) - Add pagination dots
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own
- [API Methods](/docs/api) - Programmatic control
---
# Dots Extension
Add pagination dots indicator to your carousel.
## Usage
```js
import { MotionRail } from "motionrail";
import { Dots } from "motionrail/extensions/dots";
import "motionrail/style.css";
import "motionrail/extensions/dots/style.css";
const carousel = new MotionRail(element, {
extensions: [Dots()],
});
```
## Options
### `showIndex`
- **Type**: `boolean`
- **Default**: `false`
Show item index numbers inside each dot.
```js
Dots({ showIndex: true });
```
### `log`
- **Type**: `boolean`
- **Default**: `false`
Enable console logging for debugging.
```js
Dots({ log: true });
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { Dots } from "motionrail/extensions/dots";
import "motionrail/style.css";
import "motionrail/extensions/dots/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
],
extensions: [Dots({ showIndex: true })],
});
```
### UMD (CDN)
```html
```
## Features
- **Clickable**: Click any dot to jump to that item
- **Active state**: Visual indicator for currently visible items
- **Scrollable**: Dots container scrolls when there are many items
- **Auto-center**: Active dot automatically scrolls into view
- **Accessible**: Proper ARIA labels for screen readers
## Styling
The Dots extension uses CSS variables for easy customization. You can override these variables to match your design:
```css
.motionrail-dots {
--dot-size: 34px; /* Size of each dot */
--dot-font-size: 12px; /* Font size for index numbers */
--dot-bg: rgba(128, 128, 128, 0.3); /* Dot background */
--dot-bg-hover: rgba(128, 128, 128, 0.5); /* Dot hover background */
--dot-bg-active: #666; /* Active dot background */
--dot-bg-active-hover: #555; /* Active dot hover background */
--dot-color: #999; /* Text color for index */
--dot-color-active: #fff; /* Active dot text color */
}
```
### Example Customization
```css
/* Larger dots with different colors */
.motionrail-dots {
--dot-size: 40px;
--dot-font-size: 14px;
--dot-bg: rgba(59, 130, 246, 0.3);
--dot-bg-hover: rgba(59, 130, 246, 0.5);
--dot-bg-active: #3b82f6;
--dot-bg-active-hover: #2563eb;
--dot-color: #60a5fa;
--dot-color-active: #fff;
}
```
View the default styles: [/src/extensions/dots/style.css](https://github.com/juji/motionrail/blob/main/src/extensions/dots/style.css)
## Behavior
**Single Item:** Shows one active dot.
**Multiple Items:** All visible items have active dots.
**Many Items:** Dots container becomes scrollable, active dot auto-centers.
## With Index Numbers
```js
Dots({ showIndex: true });
```
The index is displayed inside each dot (1-based for users).
## Next Steps
- [Arrows Extension](/docs/extensions/arrows) - Add navigation arrows
- [Thumbnails Extension](/docs/extensions/thumbnails) - Thumbnail navigation
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own
---
# Thumbnails Extension
Add thumbnail navigation to your carousel.
## Usage
```js
import { MotionRail } from "motionrail";
import { Thumbnails } from "motionrail/extensions/thumbnails";
import "motionrail/style.css";
import "motionrail/extensions/thumbnails/style.css";
const carousel = new MotionRail(element, {
extensions: [Thumbnails()],
});
```
## Options
### `thumbnailWidth`
- **Type**: `number`
- **Default**: `80`
Width of each thumbnail in pixels.
```js
Thumbnails({ thumbnailWidth: 120 });
```
### `thumbnailHeight`
- **Type**: `number`
- **Default**: `80`
Height of each thumbnail in pixels.
```js
Thumbnails({ thumbnailHeight: 120 });
```
### `log`
- **Type**: `boolean`
- **Default**: `false`
Enable console logging for debugging.
```js
Thumbnails({ log: true });
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { Thumbnails } from "motionrail/extensions/thumbnails";
import "motionrail/style.css";
import "motionrail/extensions/thumbnails/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
breakpoints: [{ columns: 1, gap: "16px" }],
extensions: [
Thumbnails({
thumbnailWidth: 100,
thumbnailHeight: 100,
}),
],
});
```
### UMD (CDN)
```html
```
## Features
- **Cloned content**: Automatically clones carousel item content for thumbnails
- **Clickable**: Click any thumbnail to navigate to that item
- **Active state**: Visual indicator for currently visible items
- **Scrollable**: Thumbnail container scrolls when there are many items
- **Auto-center**: Active thumbnail automatically scrolls into view
## How It Works
The extension clones the content of each carousel item to create thumbnails. This works great for images and simple content.
```html
```
The extension creates thumbnails with the same images, scaled down.
## Styling
View the default styles: [/src/extensions/thumbnails/style.css](https://github.com/juji/motionrail/blob/main/src/extensions/thumbnails/style.css)
## Next Steps
- [Dots Extension](/docs/extensions/dots) - Pagination dots
- [Arrows Extension](/docs/extensions/arrows) - Navigation arrows
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own
---
# VistaView Lightbox Extension
Open carousel images in a full-screen lightbox. Powered by [VistaView](https://vistaview.jujiplay.com) โ a zero-dependency image lightbox library.
## Installation
The extension requires both `motionrail` and `vistaview`:
```bash
npm install motionrail vistaview
```
## Usage
Import the extension and VistaView's CSS:
```js
import { MotionRail } from "motionrail";
import { VistaViewLightbox } from "motionrail/extensions/vistaview";
import "motionrail/style.css";
import "vistaview/style.css";
const carousel = new MotionRail(element, {
extensions: [VistaViewLightbox()],
});
```
The extension finds image sources by looking for an `` or `` inside each grid item (any nesting depth). If an `` is found, its `href` is used as the full-size source. Otherwise the ``'s `src` is used.
```html
```
Clicking a carousel item opens the lightbox at that image. Navigate with arrow keys, swipe gestures, or on-screen controls. After closing, clicking again re-opens the lightbox.
## Options
### `vistaViewOptions`
- **Type**: `Partial`
- **Default**: `{}`
Options passed directly to the VistaView constructor. See the [VistaView configuration docs](https://vistaview.jujiplay.com/core/configuration/complete) for all available options.
```js
VistaViewLightbox({
vistaViewOptions: {
maxZoomLevel: 3,
preloads: 2,
animationDurationBase: 400,
},
});
```
### `selector`
- **Type**: `string`
- **Default**: `[data-motionrail-grid] > *`
CSS selector for carousel items. Only change if your carousel uses a non-standard structure.
```js
VistaViewLightbox({
selector: ".carousel-item",
});
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { VistaViewLightbox } from "motionrail/extensions/vistaview";
import "motionrail/style.css";
import "vistaview/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
{ width: 1024, columns: 3, gap: "20px" },
],
extensions: [
VistaViewLightbox({
vistaViewOptions: {
maxZoomLevel: 3,
controls: {
topRight: ["zoomIn", "zoomOut", "close"],
},
},
}),
],
});
```
## How It Works
On init, the extension caches pointer start position on `pointerdown`. On `pointerup`, it compares the start and end position โ if within 5px (a click, not a swipe), it finds which grid item's bounding box contains the pointer and opens a VistaView instance at that index.
The extension bypasses DOM event targeting entirely, working around MotionRail's `pointer-events: none` on grid items.
## Next Steps
- [VistaView Documentation](https://vistaview.jujiplay.com) - Full lightbox docs
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own
---
# Logger Extension
Development extension that logs all carousel state changes, lifecycle events, and configuration to the console for easy debugging, inspection, and extension development. Can also be used as a replacement for the onChange parameter for more advanced state tracking and side effects.
## Usage
```js
import { MotionRail } from "motionrail";
import { Logger } from "motionrail/extensions/logger";
import "motionrail/style.css";
const carousel = new MotionRail(element, {
extensions: [
Logger({
// Optional hooks
onInit: (carousel, state) => {
// custom logic on init
},
onUpdate: (carousel, state) => {
// custom logic on update
},
onDestroy: (carousel, state) => {
// custom logic on destroy
},
// Control console output (default: true)
outputToConsole: true,
}),
],
});
```
## Purpose
The Logger extension is a development tool that logs carousel state and lifecycle events to the console. It's useful for:
- **Debugging**: Understanding when events fire
- **State inspection**: Seeing current carousel state
- **Extension development**: Watching the lifecycle flow
- **Testing**: Verifying behavior changes
## API
```ts
Logger(options?: {
onInit?: (carousel, state) => void;
onUpdate?: (carousel, state) => void;
onDestroy?: (carousel, state) => void;
outputToConsole?: boolean; // default: true
}): MotionRailExtension
```
- **onInit**: Called after MotionRail initializes (after logging)
- **onUpdate**: Called after each state update (after logging)
- **onDestroy**: Called after MotionRail is destroyed (after logging)
- **outputToConsole**: Set to false to silence all logger output
### What It Logs
If `outputToConsole` is true, the logger outputs messages for all extension lifecycle hooks:
- `onInit`: When the carousel is initialized
- `onUpdate`: When the carousel state updates
- `onDestroy`: When the carousel is destroyed
### State Information
Each update logs the current state:
```js
{
index: 0, // Current slide index
itemsCount: 5, // Total number of items
columns: 1, // Columns per view
gap: '16px', // Gap between items
isPlaying: true // Autoplay status
}
```
## Example Output
```
[MotionRail Logger] onInit
[MotionRail Logger] onUpdate {
index: 0,
itemsCount: 5,
columns: 1,
gap: '16px',
isPlaying: true
}
[MotionRail Logger] onUpdate {
index: 1,
itemsCount: 5,
columns: 1,
gap: '16px',
isPlaying: true
}
```
## Complete Example
```js
import { MotionRail } from "motionrail";
import { Logger } from "motionrail/extensions/logger";
import "motionrail/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
autoplay: true,
delay: 3000,
breakpoints: [
{ minWidth: "0px", columns: 1 },
{ minWidth: "768px", columns: 2 },
],
extensions: [
Logger({
outputToConsole: true,
onUpdate: (carousel, state) => {
// custom update logic
},
}),
],
});
```
### UMD (CDN)
```html
```
## When to Use
### Development
Perfect for development when you need to:
- Verify event timing
- Debug state changes
- Understand carousel behavior
- Test responsive breakpoints
### Production
**Remove the logger in production** to avoid console clutter:
````js
const extensions = [];
// Only add logger in development
if (import.meta.env.DEV) {
extensions.push(Logger());
}
const carousel = new MotionRail(element, {
extensions,
});
// Or, silence output in production:
const carousel = new MotionRail(element, {
extensions: [Logger({ outputToConsole: import.meta.env.DEV })],
});
## Conditional Loading
```js
// Only in development
const carousel = new MotionRail(element, {
extensions: [process.env.NODE_ENV === "development" && Logger()].filter(
Boolean,
),
});
````
## With Multiple Extensions
```js
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
import { Logger } from "motionrail/extensions/logger";
const carousel = new MotionRail(element, {
extensions: [
Arrows(),
Dots(),
Logger({ outputToConsole: true }), // Logs events from all extensions
],
});
```
## TypeScript
```ts
import { MotionRail } from "motionrail";
import { Logger } from "motionrail/extensions/logger";
const carousel = new MotionRail(element, {
extensions: [Logger()],
});
```
## Next Steps
- [Creating Custom Extensions](/docs/extensions/custom) - Build your own extension
- [Extensions Overview](/docs/extensions/) - Understanding the extension API
- [API Reference](/docs/api) - Full API documentation
---
# Creating Custom Extensions
Learn how to build your own MotionRail extensions.
## Extension API
Extensions are objects that implement lifecycle hooks to interact with the carousel.
```ts
interface MotionRailExtension {
name: string;
onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
}
```
See [MotionRailExtension](/docs/api/types/motionrail-extension) for complete type documentation.
## State Object
The [MotionRailState](/docs/api/types/motionrail-state) object passed to `onInit`, `onUpdate`, and `onDestroy`:
```ts
interface MotionRailState {
totalItems: number; // Total number of items in carousel
visibleItemIndexes: number[]; // Array of currently visible item indexes
isFirstItemVisible: boolean; // Whether the first item is visible
isLastItemVisible: boolean; // Whether the last item is visible
}
```
## Lifecycle Hooks
Extensions can interact with the carousel through the [MotionRail](/docs/api/class/motionrail) instance methods like `prev()`, `next()`, `scrollToIndex()`, and more.
### `onInit(motionRail, state)`
Called once when the carousel is initialized.
**Parameters:**
- `motionRail`: [MotionRail](/docs/api/class/motionrail) instance with API methods
- `state`: Initial [MotionRailState](/docs/api/types/motionrail-state)
**Use for:**
- Creating DOM elements
- Setting up event listeners
- Initial configuration
```js
onInit(motionRail, state) {
console.log('Carousel initialized with', state.totalItems, 'items');
// Create UI elements
// Attach event listeners
}
```
### `onUpdate(motionRail, state)`
Called whenever the carousel state changes (navigation, resize, etc).
**Parameters:**
- `motionRail`: [MotionRail](/docs/api/class/motionrail) instance with API methods
- `state`: Updated [MotionRailState](/docs/api/types/motionrail-state)
**Use for:**
- Updating UI based on state
- Responding to navigation
- Syncing external elements
```js
onUpdate(motionRail, state) {
console.log('Visible items:', state.visibleItemIndexes);
// Update UI elements
// Sync with other components
}
```
### `onDestroy(motionRail, state)`
Called when the carousel is destroyed.
**Parameters:**
- `motionRail`: [MotionRail](/docs/api/class/motionrail) instance
- `state`: Final [MotionRailState](/docs/api/types/motionrail-state)
**Use for:**
- Cleanup event listeners
- Remove DOM elements
- Release resources
```js
onDestroy(motionRail, state) {
// Remove event listeners
// Clean up DOM
// Release resources
}
```
## Simple Example
A counter that tracks visible items:
```js
function Counter() {
let counterElement;
return {
name: "counter",
onInit(motionRail, state) {
counterElement = document.createElement("div");
counterElement.textContent = `Showing ${state.visibleItemIndexes.length} of ${state.totalItems}`;
motionRail.container.appendChild(counterElement);
},
onUpdate(motionRail, state) {
counterElement.textContent = `Showing ${state.visibleItemIndexes.length} of ${state.totalItems}`;
},
onDestroy() {
counterElement?.remove();
},
};
}
// Usage
new MotionRail(element, {
extensions: [Counter()],
});
```
## Navigation Controls Example
A custom navigation extension:
```js
function CustomControls() {
let prevBtn, nextBtn;
return {
name: "custom-controls",
onInit(motionRail, state) {
// Create buttons
const container = document.createElement("div");
container.className = "custom-controls";
prevBtn = document.createElement("button");
prevBtn.textContent = "Previous";
prevBtn.onclick = () => motionRail.prev(); // See MotionRail.prev()
nextBtn = document.createElement("button");
nextBtn.textContent = "Next";
nextBtn.onclick = () => motionRail.next(); // See MotionRail.next()
container.append(prevBtn, nextBtn);
motionRail.container.appendChild(container);
},
onUpdate(motionRail, state) {
// Disable prev when at start
prevBtn.disabled = state.isFirstItemVisible;
// Disable next when at end
nextBtn.disabled = state.isLastItemVisible;
},
onDestroy() {
prevBtn?.remove();
nextBtn?.remove();
},
};
}
```
## Progress Bar Example
A progress indicator showing first/last item visibility:
```js
function ProgressBar() {
let progressElement;
return {
name: "progress-bar",
onInit(motionRail, state) {
progressElement = document.createElement("div");
progressElement.className = "carousel-progress";
const bar = document.createElement("div");
bar.className = "carousel-progress-bar";
progressElement.appendChild(bar);
motionRail.container.appendChild(progressElement);
},
onUpdate(motionRail, state) {
const bar = progressElement.querySelector(".carousel-progress-bar");
// Calculate progress based on scroll position
const scrollPercentage =
(motionRail.container.querySelector(".motionrail-container")
.scrollLeft /
(motionRail.container.querySelector(".motionrail-container")
.scrollWidth -
motionRail.container.querySelector(".motionrail-container")
.clientWidth)) *
100;
bar.style.width = `${scrollPercentage}%`;
},
onDestroy() {
progressElement?.remove();
},
};
}
```
**CSS:**
```css
.carousel-progress {
width: 100%;
height: 4px;
background: #e0e0e0;
margin-top: 16px;
}
.carousel-progress-bar {
height: 100%;
background: #007bff;
transition: width 0.3s ease;
}
```
## Keyboard Navigation Example
Add keyboard controls to the carousel:
```js
function KeyboardControls() {
let handleKeydown;
return {
name: "keyboard-controls",
onInit(motionRail, state) {
handleKeydown = (e) => {
if (e.key === "ArrowLeft") {
motionRail.prev();
} else if (e.key === "ArrowRight") {
motionRail.next();
}
};
document.addEventListener("keydown", handleKeydown);
},
onDestroy() {
if (handleKeydown) {
document.removeEventListener("keydown", handleKeydown);
}
},
};
}
```
## External Sync Example
Sync carousel with external UI elements:
```js
function ExternalSync(config) {
const { thumbnailContainer } = config;
return {
name: "external-sync",
onInit(motionRail, state) {
// Create thumbnail buttons for each item
for (let i = 0; i < state.totalItems; i++) {
const btn = document.createElement("button");
btn.textContent = i + 1;
btn.onclick = () => motionRail.scrollToIndex(i); // See MotionRail.scrollToIndex()
thumbnailContainer.appendChild(btn);
}
},
onUpdate(motionRail, state) {
// Update active thumbnails based on visible items
const buttons = thumbnailContainer.querySelectorAll("button");
buttons.forEach((btn, i) => {
btn.classList.toggle("active", state.visibleItemIndexes.includes(i));
});
},
onDestroy() {
thumbnailContainer.innerHTML = "";
},
};
}
// Usage
const thumbnailContainer = document.getElementById("thumbnails");
new MotionRail(element, {
extensions: [ExternalSync({ thumbnailContainer })],
});
```
## TypeScript Support
Full type definitions:
```ts
import { MotionRail } from "motionrail";
import type { MotionRailState, MotionRailExtension } from "motionrail";
function MyExtension(): MotionRailExtension {
let element: HTMLElement | null = null;
return {
name: "my-extension",
onInit(motionRail: MotionRail, state: MotionRailState) {
element = document.createElement("div");
element.textContent = `Total items: ${state.totalItems}`;
motionRail.container.appendChild(element);
},
onUpdate(motionRail: MotionRail, state: MotionRailState) {
if (element) {
element.textContent = `Visible: ${state.visibleItemIndexes.join(", ")}`;
}
},
onDestroy() {
element?.remove();
},
};
}
```
## Best Practices
### Cleanup Resources
Always clean up in `onDestroy`:
```js
onDestroy(motionRail, state) {
// Remove event listeners
document.removeEventListener('keydown', handleKeydown);
// Remove DOM elements
element?.remove();
// Clear references
element = null;
}
```
### Handle Missing Container
Check if elements exist before manipulating:
```js
onUpdate(motionRail, state) {
if (!element) return;
element.textContent = `${state.visibleItemIndexes.length} visible`;
}
```
### Use Configuration
Make extensions configurable:
```js
function MyExtension(config = {}) {
const { enabled = true, className = "default" } = config;
return {
name: "my-extension",
onInit(motionRail, state) {
if (!enabled) return;
// Use config...
},
};
}
```
### Prevent Memory Leaks
Store references properly:
```js
function MyExtension() {
let listeners = [];
return {
name: "my-extension",
onInit(motionRail, state) {
const handler = () => {
/* ... */
};
listeners.push(handler);
document.addEventListener("click", handler);
},
onDestroy() {
listeners.forEach((handler) => {
document.removeEventListener("click", handler);
});
listeners = [];
},
};
}
```
## Next Steps
- [Extensions Overview](/docs/extensions/) - Understanding the extension system
- [Arrows Extension](/docs/extensions/arrows) - Example extension source
- [Dots Extension](/docs/extensions/dots) - Another example
- [API Reference](/docs/api) - Complete API documentation
---
# MotionRail Class
The main carousel class that provides all functionality.
## Constructor
```ts
new MotionRail(element: HTMLElement, options?: MotionRailOptions)
```
**Parameters:**
- `element` - The HTML element that wraps the carousel
- `options` - Optional [MotionRailOptions](/docs/api/types/motionrail-options) configuration
**Returns:** MotionRail instance with all public methods
**Example:**
```ts
import { MotionRail } from "motionrail";
import "motionrail/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
autoplay: true,
delay: 3000,
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
],
});
```
## Properties
### `element`
The root HTML element that wraps the carousel.
- **Type:** `HTMLElement`
- **Readonly:** Yes (enforced in TypeScript only)
```ts
const carousel = new MotionRail(document.getElementById("carousel"));
console.log(carousel.element); //
...
```
::: warning
Do not modify this property directly. It is managed internally by the carousel.
:::
---
### `scrollable`
The scrollable container element (the element with `data-motionrail-scrollable` attribute).
- **Type:** `HTMLElement`
- **Readonly:** Yes (enforced in TypeScript only)
```ts
const carousel = new MotionRail(document.getElementById("carousel"));
console.log(carousel.scrollable); //
...
// You can read scroll position
console.log(carousel.scrollable.scrollLeft);
```
::: warning
Do not modify this property directly. It is managed internally by the carousel.
:::
## Methods
### Playback Control
#### `play()`
Start autoplay scrolling.
```ts
play(): void
```
**Example:**
```ts
carousel.play();
```
::: tip
Autoplay must be enabled in options for this to work.
:::
---
#### `pause()`
Pause autoplay scrolling.
```ts
pause(): void
```
**Example:**
```ts
carousel.pause();
```
### Navigation
#### `next()`
Navigate to the next page. Automatically pauses autoplay if enabled.
```ts
next(): void
```
**Example:**
```ts
carousel.next();
```
---
#### `prev()`
Navigate to the previous page. Automatically pauses autoplay if enabled.
```ts
prev(): void
```
**Example:**
```ts
carousel.prev();
```
---
#### `scrollToIndex()`
Scroll to a specific item by its index (0-based). Automatically pauses autoplay if enabled.
```ts
scrollToIndex(index: number): void
```
**Parameters:**
- `index` - Zero-based index of the item to scroll to
**Example:**
```ts
carousel.scrollToIndex(2); // Scroll to the third item
```
### State & Information
#### `getState()`
Get the current carousel state.
```ts
getState(): MotionRailState
```
**Returns:** [MotionRailState](/docs/api/types/motionrail-state) object
**Example:**
```ts
const state = carousel.getState();
console.log(state.visibleItemIndexes); // [0, 1, 2]
console.log(state.totalItems); // 10
console.log(state.isFirstItemVisible); // true
console.log(state.isLastItemVisible); // false
```
---
#### `getOptions()`
Get the current carousel configuration options. Returns a copy to prevent external modifications.
```ts
getOptions(): MotionRailOptions
```
**Returns:** [MotionRailOptions](/docs/api/types/motionrail-options) object
**Example:**
```ts
const options = carousel.getOptions();
console.log(options.autoplay); // false
console.log(options.breakpoints); // [{ columns: 1, gap: '16px' }, ...]
```
### Content Management
#### `update()`
Refresh the carousel after dynamically adding or removing items from the DOM.
```ts
update(): void
```
This method:
- Recounts total items
- Recaches snap points
- Re-observes edge items with IntersectionObserver
- Reapplies breakpoints
- Triggers onChange callback with updated state
**Example:**
```ts
// Add items to the DOM
const grid = document.querySelector("[data-motionrail-grid]");
const newItem = document.createElement("div");
newItem.textContent = "New Item";
grid.appendChild(newItem);
// Update carousel to recognize new items
carousel.update();
```
::: tip Framework Users
Framework integrations (React, Solid, Vue, Svelte) automatically call `update()` when children change. You don't need to call it manually.
:::
### Lifecycle
#### `destroy()`
Clean up event listeners, observers, and timers. Call this when removing the carousel from the DOM.
```ts
destroy(): void
```
**Example:**
```ts
// Component unmount/cleanup
function cleanup() {
if (carousel) {
carousel.destroy();
carousel = null;
}
}
```
## Complete Example
```ts
import { MotionRail } from "motionrail";
import "motionrail/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
autoplay: true,
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "16px" },
],
onChange: (state) => {
console.log("Visible items:", state.visibleItemIndexes);
},
});
// Playback controls
document.getElementById("play").addEventListener("click", () => {
carousel.play();
});
document.getElementById("pause").addEventListener("click", () => {
carousel.pause();
});
// Navigation
document.getElementById("prev").addEventListener("click", () => {
carousel.prev();
});
document.getElementById("next").addEventListener("click", () => {
carousel.next();
});
// Jump to specific item
document.querySelectorAll(".jump-button").forEach((btn, index) => {
btn.addEventListener("click", () => {
carousel.scrollToIndex(index);
});
});
// Get current state
const state = carousel.getState();
console.log("Current state:", state);
// Dynamic content
document.getElementById("add-item").addEventListener("click", () => {
const grid = document.querySelector("[data-motionrail-grid]");
const newItem = document.createElement("div");
newItem.textContent = "New Item";
grid.appendChild(newItem);
carousel.update();
});
// Cleanup on page unload
window.addEventListener("beforeunload", () => {
carousel.destroy();
});
```
## Next Steps
- [MotionRailOptions](/docs/api/types/motionrail-options) - Configuration options type
- [MotionRailState](/docs/api/types/motionrail-state) - State object type
- [Configuration](/docs/configuration) - All configuration options
- [Extensions](/docs/extensions/) - Extend functionality
---
# MotionRailOptions
Configuration options for the carousel.
## Type Definition
```ts
type MotionRailOptions = {
autoplay?: boolean;
resumeDelay?: number;
delay?: number;
rtl?: boolean;
onChange?: (state: MotionRailState) => void;
breakpoints?: MotionRailBreakpoint[];
extensions?: MotionRailExtension[];
containerName?: string;
};
```
## Properties
### `autoplay`
Enable automatic scrolling through items.
- **Type:** `boolean`
- **Default:** `false`
```ts
const carousel = new MotionRail(element, {
autoplay: true,
});
```
---
### `resumeDelay`
Milliseconds to wait before resuming autoplay after user interaction.
- **Type:** `number`
- **Default:** `3000`
```ts
const carousel = new MotionRail(element, {
autoplay: true,
resumeDelay: 5000, // Wait 5 seconds before resuming
});
```
---
### `delay`
Milliseconds between autoplay scrolls.
- **Type:** `number`
- **Default:** `3000`
```ts
const carousel = new MotionRail(element, {
autoplay: true,
delay: 4000, // Scroll every 4 seconds
});
```
---
### `rtl`
Enable right-to-left scrolling mode.
- **Type:** `boolean`
- **Default:** `false`
```ts
const carousel = new MotionRail(element, {
rtl: true,
});
```
---
### `onChange`
Callback function triggered when carousel state changes.
- **Type:** `(state: MotionRailState) => void`
- **Default:** `undefined`
```ts
const carousel = new MotionRail(element, {
onChange: (state) => {
console.log("Visible items:", state.visibleItemIndexes);
console.log("Total items:", state.totalItems);
console.log("First visible:", state.isFirstItemVisible);
console.log("Last visible:", state.isLastItemVisible);
},
});
```
---
### `breakpoints`
Responsive breakpoint configurations.
- **Type:** [MotionRailBreakpoint](/docs/api/types/motionrail-breakpoint)[]
- **Default:** `undefined`
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" }, // Base: 1 column
{ width: 768, columns: 2, gap: "24px" }, // >= 768px: 2 columns
{ width: 1024, columns: 3, gap: "32px" }, // >= 1024px: 3 columns
],
});
```
---
### `containerName`
Unique CSS container name for scoping container queries and styles to a specific carousel instance. This prevents style leakage and enables FOUC-free (Flash of Unstyled Content) rendering, especially with SSR. If not provided, a random name is generated automatically.
- **Type:** `string`
- **Default:** Randomly generated
```ts
const { containerName, containerQueries } = MotionRail.getBreakPoints({
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
{ width: 1024, columns: 3, gap: "32px" },
],
totalItems: 8,
containerName: "my-carousel-container",
});
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
{ width: 1024, columns: 3, gap: "32px" },
],
containerName: "my-carousel-container",
});
```
See [MotionRailBreakpoint](/docs/api/types/motionrail-breakpoint) and [Breakpoints Guide](/docs/breakpoints).
---
### `extensions`
Extension plugins to add functionality.
- **Type:** [MotionRailExtension](/docs/api/types/motionrail-extension)[]
- **Default:** `undefined`
```ts
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
const carousel = new MotionRail(element, {
extensions: [Arrows(), Dots()],
});
```
See [MotionRailExtension](/docs/api/types/motionrail-extension) and [Extensions](/docs/extensions/).
## Complete Example
```ts
import { MotionRail } from "motionrail";
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
import "motionrail/style.css";
import "motionrail/extensions/arrows/style.css";
import "motionrail/extensions/dots/style.css";
const carousel = new MotionRail(document.getElementById("carousel"), {
autoplay: true,
delay: 4000,
resumeDelay: 5000,
rtl: false,
onChange: (state) => {
console.log("State changed:", state);
},
breakpoints: [
{ columns: 1, gap: "16px" },
{ width: 768, columns: 2, gap: "24px" },
{ width: 1024, columns: 3, gap: "32px" },
],
extensions: [Arrows(), Dots({ dotSize: 48 })],
});
```
## Next Steps
- [MotionRailState](/docs/api/types/motionrail-state)
- [MotionRailBreakpoint](/docs/api/types/motionrail-breakpoint)
- [MotionRailExtension](/docs/api/types/motionrail-extension)
- [Configuration Guide](/docs/configuration)
---
# MotionRailState
Current state of the carousel.
## Type Definition
```ts
type MotionRailState = {
totalItems: number;
visibleItemIndexes: number[];
isFirstItemVisible: boolean;
isLastItemVisible: boolean;
};
```
## Properties
### `totalItems`
Total number of items in the carousel.
- **Type:** `number`
```ts
const state = carousel.getState();
console.log(state.totalItems); // 10
```
---
### `visibleItemIndexes`
Array of currently visible item indexes (0-based).
- **Type:** `number[]`
```ts
const state = carousel.getState();
console.log(state.visibleItemIndexes); // [0, 1, 2]
```
This array updates as the user scrolls and items enter/exit the viewport.
---
### `isFirstItemVisible`
Whether the first item is currently visible.
- **Type:** `boolean`
```ts
const state = carousel.getState();
console.log(state.isFirstItemVisible); // true
// Use for disabling "previous" button
const prevButton = document.querySelector(".prev-button");
prevButton.disabled = state.isFirstItemVisible;
```
---
### `isLastItemVisible`
Whether the last item is currently visible.
- **Type:** `boolean`
```ts
const state = carousel.getState();
console.log(state.isLastItemVisible); // false
// Use for disabling "next" button
const nextButton = document.querySelector(".next-button");
nextButton.disabled = state.isLastItemVisible;
```
## Usage
### Getting Current State
Use the `getState()` method to retrieve the current state:
```ts
const state = carousel.getState();
console.log(state);
// {
// totalItems: 10,
// visibleItemIndexes: [0, 1, 2],
// isFirstItemVisible: true,
// isLastItemVisible: false
// }
```
### Listening to State Changes
Use the `onChange` option to receive state updates:
```ts
const carousel = new MotionRail(element, {
onChange: (state) => {
console.log("Total:", state.totalItems);
console.log("Visible:", state.visibleItemIndexes);
console.log("At start:", state.isFirstItemVisible);
console.log("At end:", state.isLastItemVisible);
},
});
```
## Common Patterns
### Disable Navigation at Boundaries
```ts
const prevButton = document.querySelector(".prev");
const nextButton = document.querySelector(".next");
const carousel = new MotionRail(element, {
onChange: (state) => {
prevButton.disabled = state.isFirstItemVisible;
nextButton.disabled = state.isLastItemVisible;
},
});
```
### Update Pagination Counter
```ts
const counter = document.querySelector(".counter");
const carousel = new MotionRail(element, {
onChange: (state) => {
const currentPage = state.visibleItemIndexes[0] + 1;
counter.textContent = `${currentPage} / ${state.totalItems}`;
},
});
```
### Custom Dot Indicators
```ts
const dotsContainer = document.querySelector(".dots");
const carousel = new MotionRail(element, {
onChange: (state) => {
// Update active dot based on first visible item
const firstVisibleIndex = state.visibleItemIndexes[0];
document.querySelectorAll(".dot").forEach((dot, index) => {
dot.classList.toggle("active", index === firstVisibleIndex);
});
},
});
```
### Conditional Auto-Pause
```ts
const carousel = new MotionRail(element, {
autoplay: true,
onChange: (state) => {
// Pause when reaching the last item
if (state.isLastItemVisible) {
carousel.pause();
}
},
});
```
## Next Steps
- [MotionRail Class](/docs/api/class/motionrail)
- [MotionRailOptions](/docs/api/types/motionrail-options)
- [onChange Callback](/docs/configuration#onchange)
---
# MotionRailBreakpoint
Responsive breakpoint configuration.
## Type Definition
```ts
type MotionRailBreakpoint = {
width?: number;
columns?: number;
gap?: string;
};
```
## Properties
### `width`
Container width threshold in pixels.
- **Type:** `number`
- **Optional:** Yes (omit for base breakpoint)
Uses CSS Container Queries with `min-width` (mobile-first approach).
```ts
const breakpoints = [
{ columns: 1, gap: "16px" }, // Base (no width)
{ width: 768, columns: 2, gap: "24px" }, // >= 768px
{ width: 1024, columns: 3, gap: "32px" }, // >= 1024px
];
```
::: tip Container Width, Not Viewport
The `width` value refers to the **carousel container's width**, not the viewport/screen width. MotionRail uses CSS Container Queries internally.
:::
---
### `columns`
Number of columns to display at this breakpoint.
- **Type:** `number`
- **Optional:** Yes
```ts
const breakpoints = [
{ columns: 1 }, // 1 column
{ width: 768, columns: 2 }, // 2 columns at >= 768px
{ width: 1024, columns: 3 }, // 3 columns at >= 1024px
];
```
---
### `gap`
Gap between items as a CSS value.
- **Type:** `string`
- **Optional:** Yes
Accepts any valid CSS gap value: `px`, `rem`, `em`, `%`, etc.
```ts
const breakpoints = [
{ gap: "16px" },
{ width: 768, gap: "24px" },
{ width: 1024, gap: "2rem" },
{ width: 1280, gap: "5%" },
];
```
## Usage Patterns
### Basic Responsive Layout
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" }, // Mobile
{ width: 768, columns: 2, gap: "24px" }, // Tablet
{ width: 1024, columns: 3, gap: "32px" }, // Desktop
],
});
```
### Gap Only Changes
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ gap: "8px" }, // Small gap on mobile
{ width: 768, gap: "16px" }, // Medium gap on tablet
{ width: 1024, gap: "24px" }, // Large gap on desktop
],
});
```
### Columns Only Changes
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1 }, // 1 column
{ width: 640, columns: 2 }, // 2 columns
{ width: 1024, columns: 4 }, // 4 columns
],
});
```
### Variable Columns (1 โ 2 โ 3 โ 4)
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "16px" }, // Mobile: 1 column
{ width: 640, columns: 2, gap: "16px" }, // >= 640px: 2 columns
{ width: 1024, columns: 3, gap: "24px" }, // >= 1024px: 3 columns
{ width: 1280, columns: 4, gap: "32px" }, // >= 1280px: 4 columns
],
});
```
### Mixed Units
```ts
const carousel = new MotionRail(element, {
breakpoints: [
{ columns: 1, gap: "1rem" },
{ width: 768, columns: 2, gap: "1.5rem" },
{ width: 1024, columns: 3, gap: "2rem" },
],
});
```
## How Breakpoints Work
### Mobile-First Approach
Breakpoints use `min-width` and are applied in order from smallest to largest:
```ts
// This configuration:
[
{ columns: 1, gap: "16px" }, // Base
{ width: 768, columns: 2, gap: "24px" }, // >= 768px
{ width: 1024, columns: 3, gap: "32px" }, // >= 1024px
];
// Applies like this:
// Container width 0-767px: 1 column, 16px gap
// Container width 768-1023px: 2 columns, 24px gap
// Container width 1024px+: 3 columns, 32px gap
```
### Partial Overrides
Each breakpoint only needs to specify the properties that change:
```ts
const breakpoints = [
{ columns: 2, gap: "16px" }, // Base: 2 columns, 16px gap
{ width: 768, gap: "24px" }, // >= 768px: Still 2 columns, but 24px gap
{ width: 1024, columns: 4 }, // >= 1024px: 4 columns, still 24px gap
];
```
### Container Queries
MotionRail uses CSS Container Queries, so breakpoints respond to the **container's width**, not the viewport:
```html
...
```
```ts
// This breakpoint won't activate because the container is 600px
{ width: 768, columns: 2 }
```
## Next Steps
- [Breakpoints Guide](/docs/breakpoints) - Detailed breakpoints guide with examples
- [MotionRailOptions](/docs/api/types/motionrail-options)
- [Configuration](/docs/configuration)
---
# MotionRailExtension
Extension plugin interface.
## Type Definition
```ts
type MotionRailExtension = {
name: string;
onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
};
```
## Properties
### `name`
Unique identifier for the extension.
- **Type:** `string`
- **Required:** Yes
```ts
const customExtension: MotionRailExtension = {
name: "custom-logger",
};
```
---
### `onInit`
Called once when the carousel is initialized.
- **Type:** `(motionRail: MotionRail, state: MotionRailState) => void`
- **Optional:** Yes
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Current [MotionRailState](/docs/api/types/motionrail-state)
```ts
const customExtension: MotionRailExtension = {
name: "init-tracker",
onInit: (motionRail, state) => {
console.log("Carousel initialized with", state.totalItems, "items");
},
};
```
---
### `onUpdate`
Called whenever the carousel state updates (scroll, resize, content changes).
- **Type:** `(motionRail: MotionRail, state: MotionRailState) => void`
- **Optional:** Yes
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Updated [MotionRailState](/docs/api/types/motionrail-state)
```ts
const customExtension: MotionRailExtension = {
name: "scroll-tracker",
onUpdate: (motionRail, state) => {
console.log("Visible items:", state.visibleItemIndexes);
},
};
```
---
### `onDestroy`
Called when the carousel is destroyed for cleanup.
- **Type:** `(motionRail: MotionRail, state: MotionRailState) => void`
- **Optional:** Yes
**Parameters:**
- `motionRail` - The [MotionRail](/docs/api/class/motionrail) instance
- `state` - Final [MotionRailState](/docs/api/types/motionrail-state)
```ts
const customExtension: MotionRailExtension = {
name: "cleanup-tracker",
onDestroy: (motionRail, state) => {
console.log("Carousel destroyed");
},
};
```
## Lifecycle Order
Extensions follow this lifecycle:
1. **`onInit`** - Carousel initialization (called once)
2. **`onUpdate`** - State changes (called multiple times)
3. **`onDestroy`** - Carousel cleanup (called once)
```ts
const lifecycleExtension: MotionRailExtension = {
name: "lifecycle-demo",
onInit: (motionRail, state) => {
console.log("1. Init");
},
onUpdate: (motionRail, state) => {
console.log("2. Update (called on scroll, resize, etc.)");
},
onDestroy: (motionRail, state) => {
console.log("3. Destroy");
},
};
```
## Usage
### Adding Extensions
```ts
import { MotionRail } from "motionrail";
const carousel = new MotionRail(element, {
extensions: [customExtension1, customExtension2],
});
```
### Built-in Extensions
```ts
import { Arrows } from "motionrail/extensions/arrows";
import { Dots } from "motionrail/extensions/dots";
import { Thumbnails } from "motionrail/extensions/thumbnails";
import { Logger } from "motionrail/extensions/logger";
const carousel = new MotionRail(element, {
extensions: [Arrows(), Dots({ dotSize: 48 }), Thumbnails(), Logger()],
});
```
See [Extensions Overview](/docs/extensions/) for built-in extensions.
## Examples
### Simple Logger
```ts
const logger: MotionRailExtension = {
name: "simple-logger",
onInit: (motionRail, state) => {
console.log("Initialized");
},
onUpdate: (motionRail, state) => {
console.log("State updated:", state);
},
onDestroy: (motionRail, state) => {
console.log("Destroyed");
},
};
```
### Counter Display
```ts
const counter: MotionRailExtension = {
name: "counter",
onUpdate: (motionRail, state) => {
const counterEl = document.querySelector(".counter");
if (counterEl) {
const current = state.visibleItemIndexes[0] + 1;
counterEl.textContent = `${current} / ${state.totalItems}`;
}
},
};
```
### Custom Controls
```ts
const customControls: MotionRailExtension = {
name: "custom-controls",
onInit: (motionRail, state) => {
const prevBtn = document.querySelector(".my-prev");
const nextBtn = document.querySelector(".my-next");
prevBtn?.addEventListener("click", () => motionRail.prev());
nextBtn?.addEventListener("click", () => motionRail.next());
},
onUpdate: (motionRail, state) => {
const prevBtn = document.querySelector(".my-prev") as HTMLButtonElement;
const nextBtn = document.querySelector(".my-next") as HTMLButtonElement;
if (prevBtn) prevBtn.disabled = state.isFirstItemVisible;
if (nextBtn) nextBtn.disabled = state.isLastItemVisible;
},
};
```
### Progress Bar
```ts
const progressBar: MotionRailExtension = {
name: "progress-bar",
onInit: (motionRail, state) => {
const container = motionRail.getOptions();
const progressEl = document.createElement("div");
progressEl.className = "progress-bar";
progressEl.style.cssText =
"height: 4px; background: #ccc; position: relative;";
const bar = document.createElement("div");
bar.className = "progress-bar-fill";
bar.style.cssText =
"height: 100%; background: #007bff; width: 0; transition: width 0.3s;";
progressEl.appendChild(bar);
// Insert progress bar before carousel
},
onUpdate: (motionRail, state) => {
const bar = document.querySelector(".progress-bar-fill") as HTMLElement;
if (bar && state.totalItems > 0) {
const progress =
((state.visibleItemIndexes[0] + 1) / state.totalItems) * 100;
bar.style.width = `${progress}%`;
}
},
};
```
### Keyboard Navigation
```ts
const keyboardNav: MotionRailExtension = {
name: "keyboard-navigation",
onInit: (motionRail, state) => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "ArrowLeft") motionRail.prev();
if (e.key === "ArrowRight") motionRail.next();
};
document.addEventListener("keydown", handleKeydown);
// Store for cleanup
(motionRail as any)._keyboardHandler = handleKeydown;
},
onDestroy: (motionRail, state) => {
const handler = (motionRail as any)._keyboardHandler;
if (handler) {
document.removeEventListener("keydown", handler);
}
},
};
```
### External State Sync
```ts
const stateSync: MotionRailExtension = {
name: "state-sync",
onUpdate: (motionRail, state) => {
// Sync to external state management (Redux, Vuex, etc.)
window.dispatchEvent(
new CustomEvent("carousel-state-change", {
detail: state,
}),
);
},
};
```
## TypeScript Support
Full TypeScript support with type checking:
```ts
import { MotionRail } from "motionrail";
import type { MotionRailExtension, MotionRailState } from "motionrail";
const typedExtension: MotionRailExtension = {
name: "typed-extension",
onInit: (motionRail: MotionRail, state: MotionRailState) => {
// Full type checking and autocomplete
console.log(state.totalItems);
motionRail.play();
},
};
```
## Next Steps
- [Creating Custom Extensions](/docs/extensions/custom) - Detailed guide
- [Built-in Extensions](/docs/extensions/) - Arrows, Dots, Thumbnails, Logger
- [MotionRail Class](/docs/api/class/motionrail)
- [MotionRailState](/docs/api/types/motionrail-state)
---
# MCP Readiness
MotionRail exposes an MCP server for AI agents to interact with the library documentation and package information.
## MCP Server
- **Endpoint**: `POST /mcp`
- **Protocol**: JSON-RPC 2.0
### Tools
| Tool | Description |
| ------------------ | ------------------------------------------------------------------------- |
| `get_package_info` | Returns latest version, description, license, and repository URL from npm |
| `get_build_status` | Returns the latest GitHub Actions build status, branch, and commit |
| `search_docs` | Searches `llms-full.txt` for matching documentation content |
### Example
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "search_docs",
"params": { "query": "autoplay" }
}
```
### Client Configuration
To use the MCP server in your AI client, add to your MCP configuration:
```json
{
"mcpServers": {
"motionrail": {
"url": "https://motionrail.jujiplay.com/mcp"
}
}
}
```
## Discovery
MotionRail implements agent discovery standards:
| Resource | Path |
| --------------- | -------------------------------------- |
| MCP Server Card | `/.well-known/mcp/server-card.json` |
| Agent Skills | `/.well-known/agent-skills/index.json` |
| API Catalog | `/.well-known/api-catalog` |
## Content for AI
- [`llms.txt`](/llms.txt) โ Overview and links to individual documentation sections
- [`llms-full.txt`](/llms-full.txt) โ Complete documentation in a single file
For requesting pages as markdown, see [Markdown for Agents](/docs/ai/markdown-for-agents).
---
# Markdown for Agents
Any documentation page on MotionRail can be requested as markdown by sending an `Accept: text/markdown` header. A Cloudflare Pages middleware converts the HTML to markdown on-the-fly using [Turndown](https://github.com/mixmark-io/turndown).
## Usage
```bash
curl -H "Accept: text/markdown" https://motionrail.jujiplay.com/docs/installation
```
```bash
curl -H "Accept: text/markdown" https://motionrail.jujiplay.com/docs/api/class/motionrail
```
```bash
curl -H "Accept: text/markdown" https://motionrail.jujiplay.com/docs/quick-start
```
## How it works
1. Request comes in with `Accept: text/markdown`
2. The Pages Function middleware intercepts it
3. Fetches the rendered HTML page
4. Converts HTML to markdown via Turndown
5. Returns `Content-Type: text/markdown`
## Use cases
- AI agents consuming documentation for context
- Integrating documentation into training pipelines
- Getting a plain-text version of any page