{"id":15630294,"url":"https://github.com/richardscarrott/react-snap-carousel","last_synced_at":"2025-05-15T01:09:50.945Z","repository":{"id":64967181,"uuid":"580121348","full_name":"richardscarrott/react-snap-carousel","owner":"richardscarrott","description":"DOM-first, headless carousel for React","archived":false,"fork":false,"pushed_at":"2024-12-19T11:35:02.000Z","size":7062,"stargazers_count":324,"open_issues_count":9,"forks_count":15,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-13T23:54:09.957Z","etag":null,"topics":["carousel","react","scrolling","slideshow","typescript"],"latest_commit_sha":null,"homepage":"https://richardscarrott.github.io/react-snap-carousel/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/richardscarrott.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-19T19:18:08.000Z","updated_at":"2025-04-08T22:53:58.000Z","dependencies_parsed_at":"2024-01-20T10:33:01.603Z","dependency_job_id":"716aeffe-1c26-4b77-a8ef-458ddece6de9","html_url":"https://github.com/richardscarrott/react-snap-carousel","commit_stats":{"total_commits":76,"total_committers":4,"mean_commits":19.0,"dds":0.03947368421052633,"last_synced_commit":"d6decb451a15693daab28f119f3ecc1d88e2da40"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Freact-snap-carousel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Freact-snap-carousel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Freact-snap-carousel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Freact-snap-carousel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richardscarrott","download_url":"https://codeload.github.com/richardscarrott/react-snap-carousel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254254043,"owners_count":22039792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["carousel","react","scrolling","slideshow","typescript"],"created_at":"2024-10-03T10:31:20.755Z","updated_at":"2025-05-15T01:09:45.925Z","avatar_url":"https://github.com/richardscarrott.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# React Snap Carousel 🫰\n\n[![GitHub package.json version](https://img.shields.io/github/package-json/v/richardscarrott/react-snap-carousel.svg)](https://www.npmjs.com/package/react-snap-carousel)\n[![npm downloads](https://img.shields.io/npm/dw/react-snap-carousel)](https://www.npmjs.com/package/react-snap-carousel)\n[![CI](https://github.com/richardscarrott/react-snap-carousel/actions/workflows/node.js.yml/badge.svg)](https://github.com/richardscarrott/react-snap-carousel/actions/workflows/node.js.yml)\n[![GitHub license](https://img.shields.io/github/license/richardscarrott/react-snap-carousel.svg)](https://github.com/richardscarrott/react-snap-carousel/blob/main/LICENSE)\n\nDOM-first, headless carousel for React.\n\nReact Snap Carousel leaves the DOM in charge of scrolling and simply computes derived state from the layout, allowing you to progressively enhance a scroll element with responsive carousel controls.\n\n![Alt Text](react-snap-carousel.gif)\n\n🧈 Utilizes native browser scrolling \u0026 CSS scroll snap points for best performance and UX\n\n📏 Computes responsive page state from DOM layout \u0026 scroll position\n\n📲 Dynamic page-based CSS snap point rendering\n\n🙈 Headless design, giving you full control over UI using React Hooks API\n\n🖋️ Written in TypeScript\n\n🪶 [Lightweight (~1kB)](https://bundlephobia.com/package/react-snap-carousel) + zero dependencies\n\n## Install\n\n```\nnpm install react-snap-carousel\n```\n\n## Resources\n\n🔥[StoryBook Examples](https://richardscarrott.github.io/react-snap-carousel/)🔥\n\n[CodeSandbox StarterKit](https://codesandbox.io/s/react-snap-carousel-49vu6p?file=/src/Carousel.tsx)\n\n[Beginners Tutorial](https://dev.to/richardscarrott/build-your-own-carousel-component-with-react-snap-carousel-1e11)\n\n## Usage\n\nReact Snap Carousel doesn't expose a ready-made `\u003cCarousel /\u003e` component and instead offers a single export `useSnapCarousel` which provides the state \u0026 functions necessary to build your own carousel component.\n\nThe following code snippet is a good starting point.\n\n\u003e Inline styles are used for simplicity. You can use whichever CSS framework you prefer.\n\n\u003e You can see it in action on [CodeSandbox](https://codesandbox.io/s/react-snap-carousel-49vu6p?file=/src/Carousel.tsx).\n\n```tsx\n// Carousel.tsx\nimport React, { CSSProperties } from 'react';\nimport { useSnapCarousel } from 'react-snap-carousel';\n\nconst styles = {\n  root: {},\n  scroll: {\n    position: 'relative',\n    display: 'flex',\n    overflow: 'auto',\n    scrollSnapType: 'x mandatory'\n  },\n  item: {\n    width: '250px',\n    height: '250px',\n    flexShrink: 0\n  },\n  itemSnapPoint: {\n    scrollSnapAlign: 'start'\n  },\n  controls: {\n    display: 'flex',\n    justifyContent: 'center',\n    alignItems: 'center'\n  },\n  nextPrevButton: {},\n  nextPrevButtonDisabled: { opacity: 0.3 },\n  pagination: {\n    display: 'flex'\n  },\n  paginationButton: {\n    margin: '10px'\n  },\n  paginationButtonActive: { opacity: 0.3 },\n  pageIndicator: {\n    display: 'flex',\n    justifyContent: 'center'\n  }\n} satisfies Record\u003cstring, CSSProperties\u003e;\n\ninterface CarouselProps\u003cT\u003e {\n  readonly items: T[];\n  readonly renderItem: (\n    props: CarouselRenderItemProps\u003cT\u003e\n  ) =\u003e React.ReactElement\u003cCarouselItemProps\u003e;\n}\n\ninterface CarouselRenderItemProps\u003cT\u003e {\n  readonly item: T;\n  readonly isSnapPoint: boolean;\n}\n\nexport const Carousel = \u003cT extends any\u003e({\n  items,\n  renderItem\n}: CarouselProps\u003cT\u003e) =\u003e {\n  const {\n    scrollRef,\n    pages,\n    activePageIndex,\n    hasPrevPage,\n    hasNextPage,\n    prev,\n    next,\n    goTo,\n    snapPointIndexes\n  } = useSnapCarousel();\n  return (\n    \u003cdiv style={styles.root}\u003e\n      \u003cul style={styles.scroll} ref={scrollRef}\u003e\n        {items.map((item, i) =\u003e\n          renderItem({\n            item,\n            isSnapPoint: snapPointIndexes.has(i)\n          })\n        )}\n      \u003c/ul\u003e\n      \u003cdiv style={styles.controls} aria-hidden\u003e\n        \u003cbutton\n          style={{\n            ...styles.nextPrevButton,\n            ...(!hasPrevPage ? styles.nextPrevButtonDisabled : {})\n          }}\n          onClick={() =\u003e prev()}\n          disabled={!hasPrevPage}\n        \u003e\n          Prev\n        \u003c/button\u003e\n        {pages.map((_, i) =\u003e (\n          \u003cbutton\n            key={i}\n            style={{\n              ...styles.paginationButton,\n              ...(activePageIndex === i ? styles.paginationButtonActive : {})\n            }}\n            onClick={() =\u003e goTo(i)}\n          \u003e\n            {i + 1}\n          \u003c/button\u003e\n        ))}\n        \u003cbutton\n          style={{\n            ...styles.nextPrevButton,\n            ...(!hasNextPage ? styles.nextPrevButtonDisabled : {})\n          }}\n          onClick={() =\u003e next()}\n          disabled={!hasNextPage}\n        \u003e\n          Next\n        \u003c/button\u003e\n      \u003c/div\u003e\n      \u003cdiv style={styles.pageIndicator}\u003e\n        {activePageIndex + 1} / {pages.length}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n\ninterface CarouselItemProps {\n  readonly isSnapPoint: boolean;\n  readonly children?: React.ReactNode;\n}\n\nexport const CarouselItem = ({ isSnapPoint, children }: CarouselItemProps) =\u003e (\n  \u003cli\n    style={{\n      ...styles.item,\n      ...(isSnapPoint ? styles.itemSnapPoint : {})\n    }}\n  \u003e\n    {children}\n  \u003c/li\u003e\n);\n```\n\n```tsx\n// App.tsx\nimport { Carousel, CarouselItem } from './Carousel';\n\nconst items = Array.from({ length: 20 }).map((_, i) =\u003e ({\n  id: i,\n  src: `https://picsum.photos/500?idx=${i}`\n}));\n\nconst App = () =\u003e (\n  \u003cCarousel\n    items={items}\n    renderItem={({ item, isSnapPoint }) =\u003e (\n      \u003cCarouselItem key={item.id} isSnapPoint={isSnapPoint}\u003e\n        \u003cimg src={item.src} width=\"250\" height=\"250\" alt=\"\" /\u003e\n      \u003c/CarouselItem\u003e\n    )}\n  /\u003e\n);\n\nexport default App;\n```\n\n## Api\n\n### `useSnapCarousel(options)`\n\n#### Parameters\n\n##### Options\n\n```ts\ninterface SnapCarouselOptions {\n  // Horizontal or vertical carousel\n  readonly axis?: 'x' | 'y';\n  // Allows you to render pagination during SSR\n  readonly initialPages?: number[][];\n}\n```\n\n#### Return value\n\n```ts\nexport interface SnapCarouselResult {\n  readonly pages: number[][];\n  readonly activePageIndex: number;\n  readonly snapPointIndexes: Set\u003cnumber\u003e;\n  readonly hasPrevPage: boolean;\n  readonly hasNextPage: boolean;\n  readonly prev: (opts?: SnapCarouselGoToOptions) =\u003e void;\n  readonly next: (opts?: SnapCarouselGoToOptions) =\u003e void;\n  readonly goTo: (pageIndex: number, opts?: SnapCarouselGoToOptions) =\u003e void;\n  readonly refresh: () =\u003e void;\n  readonly scrollRef: (el: HTMLElement | null) =\u003e void;\n}\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardscarrott%2Freact-snap-carousel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichardscarrott%2Freact-snap-carousel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardscarrott%2Freact-snap-carousel/lists"}