# 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 ``` ::: 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 ``` ## 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} ``` ::: 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 (
Slide 1
Slide 2
Slide 3
); } ``` ### Vanilla JavaScript ```js {9} import { MotionRail } from "motionrail"; import { Arrows } from "motionrail/extensions/arrows"; import "motionrail/style.css"; import "motionrail/extensions/arrows/style.css"; const container = document.querySelector(".carousel-container"); const carousel = new MotionRail(container, { rtl: true, extensions: [Arrows()], }); ``` ```html {1} ``` ## See Also - [Configuration](/docs/configuration) - All available configuration options - [Arrows Extension](/docs/extensions/arrows) - Navigation arrows that work with RTL - [Thumbnails Extension](/docs/extensions/thumbnails) - Thumbnail navigation with RTL support --- # API Methods [MotionRail](/docs/api/class/motionrail) provides a comprehensive API for programmatic control of the carousel. ```js const carousel = new MotionRail(element, options); // Playback Control carousel.play(); // Start autoplay carousel.pause(); // Pause autoplay // Navigation carousel.next(); // Navigate to next page carousel.prev(); // Navigate to previous page carousel.scrollToIndex(index); // Scroll to specific item // State & Information carousel.getState(); // Get current carousel state carousel.getOptions(); // Get current options // Content Management carousel.update(); // Refresh after DOM changes // Lifecycle carousel.destroy(); // Clean up and remove ``` ## Playback Control ### `play()` Start autoplay scrolling. ```js carousel.play(); ``` ::: tip Autoplay must be enabled in options for this to work. ::: ### `pause()` Pause autoplay scrolling. ```js carousel.pause(); ``` ## Navigation ### `next()` Navigate to the next page. Automatically pauses autoplay if enabled. ```js carousel.next(); ``` **Example with button:** ```js document.getElementById("next-btn").addEventListener("click", () => { carousel.next(); }); ``` ### `prev()` Navigate to the previous page. Automatically pauses autoplay if enabled. ```js carousel.prev(); ``` **Example with button:** ```js document.getElementById("prev-btn").addEventListener("click", () => { carousel.prev(); }); ``` ### `scrollToIndex(index: number)` Scroll to a specific item by its index (0-based). Automatically pauses autoplay if enabled. ```js carousel.scrollToIndex(2); // Scroll to the third item ``` **Example with pagination:** ```js const dots = document.querySelectorAll(".dot"); dots.forEach((dot, index) => { dot.addEventListener("click", () => { carousel.scrollToIndex(index); }); }); ``` ## State & Information ### `getState()` Get the current carousel state. **Returns:** [MotionRailState](/docs/api/types/motionrail-state) ```js 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 ``` **State object:** ```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 } ``` ### `getOptions()` Get the current carousel configuration options. Returns a copy to prevent external modifications. **Returns:** [MotionRailOptions](/docs/api/types/motionrail-options) ```js 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. This method: - Recounts total items - Recaches snap points - Re-observes edge items with IntersectionObserver - Reapplies breakpoints - Triggers onChange callback with updated state ```js // 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(); ``` **Example: Dynamic add/remove** ```js const addButton = document.getElementById("add-item"); const removeButton = document.getElementById("remove-item"); let itemCounter = 6; addButton.addEventListener("click", () => { const grid = document.querySelector("[data-motionrail-grid]"); const newItem = document.createElement("div"); newItem.textContent = `Item ${itemCounter++}`; grid.appendChild(newItem); carousel.update(); }); removeButton.addEventListener("click", () => { const grid = document.querySelector("[data-motionrail-grid]"); if (grid.children.length > 0) { grid.removeChild(grid.lastChild); 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. ```js carousel.destroy(); ``` **Example: SPA cleanup** ```js // Component unmount/cleanup function cleanup() { if (carousel) { carousel.destroy(); carousel = null; } } ``` ## Complete Example ```js 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) => { // Update UI based on state updatePaginationDots(state); toggleNavigationButtons(state); }, }); // 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 document.getElementById("get-state").addEventListener("click", () => { 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 - [MotionRail Class](/docs/api/class/motionrail) - Class documentation with properties - [Configuration](/docs/configuration) - All configuration options - [Extensions](/docs/extensions/) - Extend functionality - [Framework Integrations](/docs/frameworks/react) - Use with frameworks --- # React Integration MotionRail provides a first-class React component with full TypeScript support. ## FOUC-Free Styling Example ```tsx import { MotionRail } from "motionrail/react"; import { MotionRail as MotionRailClass } from "motionrail"; import "motionrail/style.css"; export default function Home() { // 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) => (
{/* ...carousel item content... */}
))}
); } ``` ## Basic Usage ```jsx import { MotionRail } from "motionrail/react"; import "motionrail/style.css"; // Define options outside to prevent re-renders const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; function App() { return (
Item 1
Item 2
Item 3
); } ``` ## Props ### `options` - **Type**: [MotionRailOptions](/docs/api/types/motionrail-options) - **Required**: No Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options. ```jsx const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, ], }; {/* items */}; ``` ### `ref` - **Type**: `RefObject` - **Required**: No Ref to access the MotionRail instance for programmatic control. ```jsx import { useRef } from "react"; const options = {}; function MyCarousel() { const carouselRef = useRef(null); const handleNext = () => { carouselRef.current?.next(); }; return ( <>
Item 1
Item 2
); } ``` ### `divRef` - **Type**: `RefObject` - **Required**: No Ref to access the container HTMLDivElement. ```jsx import { useRef } from "react"; const options = {}; function MyCarousel() { const containerRef = useRef(null); return (
Item 1
Item 2
); } ``` ### Other Props All other props are passed to the root `div` element: ```jsx const options = {}; {/* items */} ; ``` ## Complete Example ```jsx import { useRef, useState } from "react"; import { MotionRail } from "motionrail/react"; import "motionrail/style.css"; function Carousel() { const carouselRef = useRef(null); const containerRef = useRef(null); const [currentState, setCurrentState] = useState(null); const handleNext = () => { carouselRef.current?.next(); }; const handlePrev = () => { carouselRef.current?.prev(); }; const handlePlay = () => { carouselRef.current?.play(); }; const handlePause = () => { carouselRef.current?.pause(); }; // Define options inside only when using setState (setCurrentState) const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, { width: 1024, columns: 3, gap: "20px" }, ], onChange: setCurrentState, }; return (
Item 1
Item 2
Item 3
Item 4
Item 5
{currentState && (

Visible items: {currentState.visibleItemIndexes.join(", ")}

Total items: {currentState.totalItems}

)}
); } ``` ## Dynamic Children The React component automatically calls `update()` when children change: ```jsx import { useState } from "react"; import { MotionRail } from "motionrail/react"; // Define options outside to prevent re-renders const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; function DynamicCarousel() { const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"]); const addItem = () => { setItems([...items, `Item ${items.length + 1}`]); }; const removeItem = () => { setItems(items.slice(0, -1)); }; return ( <> {items.map((item, index) => (
{item}
))}
); } ``` ## TypeScript Full TypeScript support is included: ```tsx import { useRef } from "react"; import { MotionRail } from "motionrail/react"; import type { MotionRailOptions, MotionRailState } from "motionrail"; function TypedCarousel() { const carouselRef = useRef>(null); const options: MotionRailOptions = { autoplay: true, breakpoints: [{ columns: 3, gap: "20px" }], onChange: (state: MotionRailState) => { console.log(state); }, }; return (
Item 1
Item 2
); } ``` ## Bundle Size **React integration**: 0.87 kB (gzipped: 0.42 kB) ## Next Steps - [Configuration](/docs/configuration) - All configuration options - [API Methods](/docs/api) - Programmatic control - [Extensions](/docs/extensions/) - Add more functionality --- # Preact Integration MotionRail provides a first-class Preact component with full TypeScript support. ## FOUC-Free Styling Example ```tsx import { MotionRail } from "motionrail/preact"; import { MotionRail as MotionRailClass } from "motionrail"; import "motionrail/style.css"; export default function Home() { // 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) => (
{/* ...carousel item content... */}
))}
); } ``` ## Basic Usage ```jsx import { MotionRail } from "motionrail/preact"; import "motionrail/style.css"; // Define options outside to prevent re-renders const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; function App() { return (
Item 1
Item 2
Item 3
); } ``` ## Props ### `options` - **Type**: [MotionRailOptions](/docs/api/types/motionrail-options) - **Required**: No Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options. ```jsx const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, ], }; {/* items */}; ``` ### Other Props All other props are passed to the root `div` element: ```jsx const options = {}; {/* items */} ; ``` ::: warning Unlike React, Preact does not expose refs to access the MotionRail instance. If you need programmatic control, consider using the vanilla JavaScript API directly. ::: ## Complete Example ```jsx import { useState } from "preact/hooks"; import { MotionRail } from "motionrail/preact"; import "motionrail/style.css"; function Carousel() { const [currentState, setCurrentState] = useState(null); const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, { width: 1024, columns: 3, gap: "20px" }, ], onChange: setCurrentState, }; return (
Item 1
Item 2
Item 3
Item 4
Item 5
{currentState && (

Visible items: {currentState.visibleItemIndexes.join(", ")}

Total items: {currentState.totalItems}

)}
); } ``` ## Dynamic Children The Preact component automatically calls `update()` when children change: ```jsx import { useState } from "preact/hooks"; import { MotionRail } from "motionrail/preact"; // Define options outside to prevent re-renders const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; function DynamicCarousel() { const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"]); const addItem = () => { setItems([...items, `Item ${items.length + 1}`]); }; const removeItem = () => { setItems(items.slice(0, -1)); }; return ( <> {items.map((item, index) => (
{item}
))}
); } ``` ## TypeScript Full TypeScript support is included: ```tsx import { MotionRail } from "motionrail/preact"; import type { MotionRailOptions, MotionRailState } from "motionrail"; function TypedCarousel() { const options: MotionRailOptions = { autoplay: true, breakpoints: [{ columns: 3, gap: "20px" }], onChange: (state: MotionRailState) => { console.log(state); }, }; return (
Item 1
Item 2
); } ``` ## Bundle Size **Preact integration**: 0.82 kB (gzipped: 0.40 kB) ## Next Steps - [Configuration](/docs/configuration) - All configuration options - [API Methods](/docs/api) - Programmatic control - [Extensions](/docs/extensions/) - Add more functionality --- # Solid.js Integration MotionRail provides a first-class Solid.js component with full TypeScript support. ## FOUC-Free Styling Example ```tsx import { Style } from "@solidjs/meta"; import { clientOnly } from "@solidjs/start"; import { MotionRail as MotionRailClass } from "motionrail"; import "motionrail/style.css"; const MotionRail = clientOnly(() => import("motionrail/solid").then((m) => ({ default: m.MotionRail })), ); // 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, }); export default function Home() { return (
{/* FOUC prevention: container query style in head, generated dynamically */} {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
{/* ...carousel item content... */}
))}
); } ``` ## Basic Usage ```jsx import { MotionRail } from "motionrail/solid"; import "motionrail/style.css"; // Define options outside to prevent re-renders const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; function App() { return (
Item 1
Item 2
Item 3
); } ``` ## Props ### `options` - **Type**: [MotionRailOptions](/docs/api/types/motionrail-options) - **Required**: No Configuration options for the carousel. See [Configuration](/docs/configuration) for all available options. ```jsx const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, ], }; {/* items */}; ``` ### `ref` - **Type**: `(instance: MotionRailClass) => void` - **Required**: No Callback that receives the MotionRail instance for programmatic control. ```jsx const options = {}; function MyCarousel() { let carousel; const handleNext = () => { carousel?.next(); }; return ( <> (carousel = instance)} options={options}>
Item 1
Item 2
); } ``` ### `divRef` - **Type**: `(element: HTMLDivElement) => void` - **Required**: No Callback that receives the container HTMLDivElement. ```jsx const options = {}; function MyCarousel() { let container; return ( (container = el)} options={options}>
Item 1
Item 2
); } ``` ### Other Props All other props are passed to the root `div` element: ```jsx const options = {}; {/* items */} ; ``` ## Complete Example ```jsx import { createSignal } from "solid-js"; import { MotionRail } from "motionrail/solid"; import "motionrail/style.css"; function Carousel() { let carousel; let container; const [currentState, setCurrentState] = createSignal(null); const handleNext = () => { carousel?.next(); }; const handlePrev = () => { carousel?.prev(); }; const handlePlay = () => { carousel?.play(); }; const handlePause = () => { carousel?.pause(); }; // Define options inside only when using signals (setCurrentState) const options = { autoplay: true, delay: 3000, breakpoints: [ { columns: 1, gap: "16px" }, { width: 768, columns: 2, gap: "16px" }, { width: 1024, columns: 3, gap: "20px" }, ], onChange: setCurrentState, }; return (
(carousel = instance)} divRef={(el) => (container = el)} options={options} class="my-carousel" >
Item 1
Item 2
Item 3
Item 4
Item 5
{currentState() && (

Visible items: {currentState().visibleItemIndexes.join(", ")}

Total items: {currentState().totalItems}

)}
); } ``` ## 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 ``` ## 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 ``` ### 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 ``` ## TypeScript Full TypeScript support with ` ``` ## 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
Item 1
Item 2
Item 3
Item 4
Item 5
{#if currentState}

Visible items: {currentState.visibleItemIndexes.join(', ')}

Total items: {currentState.totalItems}

{/if}
``` ## 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) => (
{/* ...carousel item content... */}
))}
); }); ``` ## Basic Usage ```tsx import { component$ } from "@builder.io/qwik"; import { MotionRail } from "motionrail/qwik"; import "motionrail/style.css"; export default component$(() => { const options = { breakpoints: [{ columns: 3, gap: "20px" }] }; return (
Item 1
Item 2
Item 3
); }); ``` ## 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 (
{ currentState.value = state; }, })} class="my-carousel" >
Item 1
Item 2
Item 3
Item 4
Item 5
{currentState.value && (

Visible items: {currentState.value.visibleItemIndexes.join(", ")}

Total items: {currentState.value.totalItems}

)}
); }); ``` ## 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