# OrbitGallery — 3D Carousel for React & Three.js (WebGL)

> A 3D WebGL carousel for React and Next.js: project cards on a curved arc above a reflective floor. Drag, scroll or use the arrow keys, then click a card to open it. Three layouts, light and dark themes, accessible and SEO-friendly.

- Kind: Component (Carousels)
- Price: comes with the Pro plan
- License: UpFork Standard License. Use it in unlimited personal and commercial projects. Don't resell or redistribute it on its own.
- Developer: UpFork
- Tags: carousel, gallery, hero, 3d-carousel, webgl, threejs, react-three-fiber, nextjs, portfolio, image-gallery, animation
- npm packages: @react-three/drei, @react-three/fiber, @react-three/postprocessing, gsap, postprocessing, three
- Published: 2026-10-04
- Updated: 2026-10-04
- Page: https://upfork.dev/products/orbit-gallery-3d-carousel-react

## How to get it

Install it into a React + Tailwind project with the shadcn CLI:

```bash
npx shadcn@latest add @upfork/orbit-gallery-3d-carousel-react
```

Every install uses the member's personal CLI token (UPFORK_TOKEN), made on their UpFork account page. It comes with UpFork's Pro plan and the plans after it.

## About

Project cards on a curved arc above a reflective floor. Drag, scroll or use the arrow keys to turn it; click the
front card and it flies to fill the screen while the project opens over it. A sellable React component with a
headless core, three layouts, light, dark and auto themes, an accessible and indexable list, and a CSS fallback when
there is no WebGL.

![The arc of project cards over a reflective floor](demo/screenshots/01-idle-arc.jpg)

| | |
| --- | --- |
| ![Hover](demo/screenshots/02-hover.jpg) | ![A fast drag](demo/screenshots/03-fast-drag.jpg) |
| ![An open project](demo/screenshots/04-detail.jpg) | ![Grid layout](demo/screenshots/05-grid.jpg) |
| ![Stack layout](demo/screenshots/06-stack.jpg) | ![Light theme](demo/screenshots/07-light-theme.jpg) |

## What's here

```text
package/          the component: source, tests, build (tsup: ESM + CJS + .d.ts), README with every prop and recipe
  src/            OrbitGallery, the scene (cards, floor, camera rig, effects), the headless store and hooks,
                  the layouts, the HTML layer (overlay, cursor, detail view, accessible list), the CSS fallback
  test/           vitest: layout geometry, seamless wrap, snapping, the state machine, the hook
demo/             the landing page (Vite, prerendered to static HTML) with a leva panel and "Copy props"
  src/data/       the twelve demo projects (items.ts) and their blur-up placeholders
  public/media/   their covers (WebP) and the two loops (MP4)
  art/            the covers and loops are drawn in code: art.html holds the shaders, render.mjs renders them
  e2e/            the Playwright smoke test
  screenshots/    the screenshots above, made by scripts/screenshots.mjs
registry/         the component as flat files for UpFork's live preview and the shadcn CLI (scripts/registry.mjs)
PLAN.md           the plan written before the code, and where the build departs from the brief
LISTING.md        what goes into UpFork's product form
```

The component's documentation is [package/README.md](package/README.md).

## Run it

Node.js 22 and pnpm.

```bash
pnpm install
pnpm dev          # the demo with live reload, http://localhost:4101
pnpm build        # the library (package/dist) and the prerendered demo (demo/dist)
pnpm preview      # the built demo, http://localhost:4100
pnpm test         # 46 unit tests
pnpm e2e          # the Playwright smoke test (builds and serves the demo itself; uses the installed Chrome)
pnpm lint
pnpm typecheck
```

`pnpm --filter orbit-gallery size` prints the library's gzipped size after a build (about 39 KB; the budget is 60).

The demo reads a few settings from the address bar, handy for links and screenshots:
`/?layout=grid&theme=light&effects=cinematic#full`.

## Make it yours

- **Projects**: `demo/src/data/items.ts`. Each item needs an `id`, a `title` and an `image`; a `subtitle`,
  `description`, `tags`, `href`, `video`, `accent` and a 24 px `placeholder` are optional. The demo's are fictional.
- **Corners**: the `brand`, `nav` and `cta` props take any markup (see `demo/src/App.tsx`).
- **Look**: the `theme` prop (eight tokens: colours and the two fonts), `radius`, `curvature`, `gap`, `cardAspect`,
  `floor` and `effects`. Open **Tune** on the demo, set it up by eye, then **Copy props**.
- **Cover art**: `demo/art/art.html` draws each demo picture in a fragment shader. `node art/render.mjs preview <dir>`
  renders previews, `stills <dir>` the 4096 × 2560 originals, `frames <dir> <name> <seconds> <fps>` a seamless loop;
  `python art/build-media.py <stills> [<framesRoot>]` writes the WebP covers, the placeholders and the MP4s.

## Checks

Verified in Chrome (Windows, Intel Iris Plus) on the production build:

- Lighthouse, desktop: Performance 91–92, Accessibility 100, Best practices 100, SEO 100 (LCP 0.4 s, CLS 0.001).
- No console errors or warnings (three is pinned to 0.182: three r183+ makes React Three Fiber 9 warn about
  `THREE.Clock`).
- Twenty open/close cycles: geometries, textures and programs stay flat, the JS heap stays at 10 MB.
- A Next.js 16 App Router app builds and runs with it both through `next/dynamic` and as a plain server-rendered
  import.
- Without WebGL (`--disable-gpu`), the CSS fallback renders and responds to the keys.

## Licence

Sold on UpFork under the UpFork Standard License: see [package/LICENSE.md](package/LICENSE.md).
