$ show davidjerleke/embla-carousel
Embla Carousel is a dependency-free, framework-agnostic carousel/slider library written in TypeScript. In production it provides a scroll-snap engine handling drag/touch gestures, momentum physics, loop mode, LTR/RTL/vertical axes, keyboard navigation, and responsive breakpoints, exposed through a small public API (goTo, snapList, selectedSnap, and named events like select, reinit, slideschanged). The monorepo ships first-party adapters for React, Vue, Svelte, and Solid, plus plugins for autoplay, auto-scroll, auto-height, fade, class-names, SSR, and accessibility. It also contains a Next.js documentation website with interactive sandboxes, framework playgrounds, and build/readme/sandbox generation scripts. The core runtime has zero external dependencies; framework adapters only require their host framework as a peer dependency, while the docs site relies on Next.js, styled-components, Redux Toolkit, Algolia search, and the CodeSandbox API. Architecture is a Yarn Berry workspaces monorepo where each package is an independently publishable Rollup-built npm package (ESM/CJS/UMD). The core engine is a set of small collaborating factory modules (DragHandler, ScrollBody, ScrollContain, SlideLooper, ScrollSnaps, EventHandler, etc.) composed by an Engine and driven by the top-level EmblaCarousel module. Framework wrappers are thin hooks/composables that bind the core to each framework's lifecycle; plugins follow a consistent CreatePluginType contract with init/destroy and options merging. Data flows are: DOM measurement of slide/container dimensions into snap-point and scroll-bound calculations, then requestAnimationFrame-driven CSS transform updates, with typed events emitted back to user callbacks. Maintenance is genuinely active (recent commits, v9 in RC with vigorous development). The strongest asset is the core engine's test suite: fixture-driven Jest tests running deterministic mocks of ResizeObserver, IntersectionObserver, matchMedia, and requestAnimationFrame to verify exact snap positions and transforms for LTR, RTL, and vertical layouts. The main hygiene issues are committed Next.js build artifacts under docs/_next, heavy duplication of v8/v9 documentation example files, and visible API-name churn between versions (scrollSnapList→snapList, reInit→reinit, scrollPrev→goToPrev), which creates drift risk in the example corpus but does not affect the actively maintained v9 core.
The LLM identified 5 files worth highlighting for reuse:
Implements the scroll animation physics: a self-contained motion model converting target offsets into time-animated translation with configurable duration/easing and velocity-aware settling. This is the module behind Embla's signature fluid motion and is pure math with no DOM or framework coupling. Other plugins (e.g. auto-scroll) replace it with custom seek functions, demonstrating a clean interface. It could be extracted into any animation or scrolling library with minimal changes.
A typed event emitter whose emit() returns a boolean so listeners can cancel the event's default behavior — an inversion of the standard observer pattern that is genuinely useful for plugin systems. It powers all carousel events (select, reinit, slideschanged) and plugin hooks, is framework-free, and is small enough to lift directly into any TypeScript project needing typed, cancellable event dispatch.
Pure-function snap containment logic (trimSnaps/keepSnaps): given scroll limits and slide positions, it constrains snap points so the carousel cannot rest outside legal bounds, handling the different containScroll strategies and loop edge cases. It operates only on number arrays with no DOM/framework imports, making it an easily extractable constraint-satisfaction algorithm for any paging, scroll-snap, or virtual-scroll component.
Encapsulates the hard problem of pointer/touch drag interaction: tracking movement, computing drag force and release velocity for momentum handoff, dismissing clicks after a drag, and coordinating with scroll bounds and the animation loop. It has no framework-specific code and exposes a small interface, making it reusable in any gesture-driven UI that needs tap-vs-swipe-vs-drag disambiguation.
A set of hand-rolled mocks for requestAnimationFrame, ResizeObserver, IntersectionObserver, MutationObserver, and matchMedia that allow the engine to be tested deterministically in plain Jest with no DOM library. Combined with the typed dimension fixtures (exact offsets, margins, and end-margins for LTR/RTL/vertical cases), this is a reusable pattern for any DOM-measuring TypeScript library that needs stable, browser-API-free unit tests.