---
title: "How to Build a 3D Circular Showcase in Framer"
description: "Build a 3D circular showcase in Framer using CSS 3D transforms — the ring layout math, drag-to-rotate interaction, and a drop-in component option."
canonical_url: "https://framerhub.io/blog/3d-circular-showcase-framer"
last_updated: "2027-02-10T00:00:00.000Z"
---

A 3D circular showcase — a ring of cards or images arranged in three-dimensional space that rotates to bring each item to the front — is one of the more visually impressive effects you can build with pure CSS, no WebGL or canvas required. The trick is entirely geometric: place items around a circle using rotation and depth translation, then spin the whole ring. Here's the math and the code.

## The Core Geometry

Every item in the ring needs two transforms applied together:

1. **Rotate around the Y-axis** by an angle equal to `360° ÷ itemCount × itemIndex` — this points each item in its own direction around the circle.
2. **Translate outward along the Z-axis** by the ring's radius — this pushes the now-rotated item away from the center, out to the edge of the circle.

Applied to every item with its own index-based rotation, the combined effect places all items evenly spaced around a 3D ring, all facing outward from the center — like the numbers around a clock face, but in 3D space with actual visible depth as you rotate the group.

What makes this effect genuinely three-dimensional rather than a flat illusion is that the browser is actually computing real depth — items on the far side of the ring are genuinely farther from the viewer in 3D space, which is why they appear smaller and why nearer items can occlude them correctly as the ring rotates, without any manual z-index management on your part. This is a meaningful advantage over trying to fake circular depth with 2D scaling and opacity tricks, which tend to fall apart the moment a visitor interacts with the showcase from an angle the original 2D approximation wasn't designed for.

## Why This Reads as More Premium Than a Standard Carousel

A flat, horizontal carousel communicates "here are some items, one at a time." A 3D ring communicates something closer to "here is a curated, dimensional object you can turn and explore" — the same underlying content (a set of images or cards) but a materially different feeling of craft and intentionality. That perceived value bump is exactly why this pattern shows up disproportionately often on premium product showcases, agency portfolio "our work" sections, and brand moments specifically designed to signal quality in the first few seconds of a page — the visual complexity of the effect itself becomes part of the message.

## Building It With a Code Override

```jsx
import { motion, useMotionValue, useSpring, useTransform } from "motion/react"
import { addPropertyControls, ControlType } from "framer"

export default function CircularShowcase(props) {
  const { images, radius, itemSize, autoRotate, rotateSpeed } = props
  const list = images && images.length ? images : []
  const count = list.length || 1
  const rotationY = useMotionValue(0)
  const smoothRotation = useSpring(rotationY, { stiffness: 60, damping: 20 })

  function handleDrag(_, info) {
    rotationY.set(rotationY.get() + info.delta.x * 0.4)
  }

  return (
    <div
      style={{
        perspective: 1200,
        width: "100%",
        height: itemSize * 1.4,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <motion.div
        drag="x"
        dragElastic={0}
        dragMomentum={false}
        onDrag={handleDrag}
        style={{
          position: "relative",
          width: itemSize,
          height: itemSize,
          transformStyle: "preserve-3d",
          rotateY: smoothRotation,
          cursor: "grab",
        }}
        animate={autoRotate ? { rotateY: 360 } : undefined}
        transition={autoRotate ? { duration: rotateSpeed, repeat: Infinity, ease: "linear" } : undefined}
      >
        {list.map((src, i) => {
          const angle = (360 / count) * i
          return (
            <div
              key={i}
              style={{
                position: "absolute",
                width: itemSize,
                height: itemSize,
                borderRadius: 16,
                overflow: "hidden",
                transform: `rotateY(${angle}deg) translateZ(${radius}px)`,
                backfaceVisibility: "hidden",
              }}
            >
              <img src={src} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
            </div>
          )
        })}
      </motion.div>
    </div>
  )
}

addPropertyControls(CircularShowcase, {
  images: { type: ControlType.Array, control: { type: ControlType.Image } },
  radius: { type: ControlType.Number, defaultValue: 280, min: 100, max: 600 },
  itemSize: { type: ControlType.Number, defaultValue: 200, min: 100, max: 400 },
  autoRotate: { type: ControlType.Boolean, defaultValue: true },
  rotateSpeed: { type: ControlType.Number, defaultValue: 20, min: 8, max: 60 },
})
```

A handful of details make this actually work correctly rather than just approximately:

- **perspective on the outer container, not the rotating group itself** — perspective needs to be set on the parent to establish the 3D viewing context that the child's `transform-style: preserve-3d` and individual `translateZ` values render into. Set it on the wrong element and the 3D depth simply won't render.
- **transformStyle: "preserve-3d" on the rotating group** — without this, child elements' individual 3D transforms (`rotateY` + `translateZ` per item) collapse into a flat 2D plane instead of actually existing at different depths.
- **useSpring wrapping the drag-driven rotation value** — this is what gives drag interaction a natural, weighted momentum feel rather than the ring snapping instantly to follow the cursor, which would feel mechanical rather than physical.
- **backfaceVisibility: "hidden"** on each item hides the "back" of an item when it's rotated to face away from the viewer, preventing a mirrored, flipped version of the image from being visible through itself during rotation.

## Solving the Radius Math

Getting the radius right for a given item count and size is the part most first attempts fumble by trial and error rather than calculation. The formula that keeps adjacent items just touching, edge to edge, without overlapping or spreading too far apart:

```text
radius = itemSize / (2 × tan(180° / itemCount))
```

For example, with 8 items at 200px each: `200 / (2 × tan(22.5°)) ≈ 200 / 0.828 ≈ 241px`. Use this as a starting point, then adjust up for more breathing room between items or down for a tighter, denser ring — the formula gives you the mathematically "just touching" baseline, not necessarily the most visually pleasing final value, which often benefits from 10-20% more radius than the strict minimum.

## Auto-Rotate vs. Drag-Only

- **Auto-rotate with drag override** (as in the snippet, using both an `animate` loop and drag interaction) works well for a passive hero or brand moment — it's visually alive even before a visitor interacts, and dragging naturally interrupts the automatic spin to let someone browse manually.
- **Drag/click-only** (no automatic motion) suits a showcase where you want visitors to deliberately explore items — a product configurator or portfolio piece selector, for instance — where constant motion would be distracting rather than inviting.

If combining both, make sure a manual drag interaction cleanly pauses the automatic rotation rather than fighting it — the snippet above achieves this implicitly since `onDrag` directly sets `rotationY`, which the spring then smooths, effectively overriding whatever the `animate` loop was doing.

## Touch and Mobile Considerations

Framer Motion's `drag="x"` handles touch and mouse input through the same API, so the drag interaction in the snippet works on mobile without separate touch event handling. Where mobile does need separate tuning: reduce `itemSize` and `radius` proportionally to viewport width, and consider reducing the item count itself on narrow screens — a 10-item ring that reads clearly on a wide desktop viewport can feel cramped and hard to parse individually on a phone screen, where there's simply less width for depth and spacing to register visually.

## Drop-In Option: Circular Showcase

FramerHub's [Circular Showcase component](/components/circular-showcase) implements this exact pattern — ring layout, radius, auto-rotate, and drag interaction all exposed as property panel controls, with the radius math already solved for you regardless of item count. It's part of FramerHub's [component library](/components), available through the [Components plugin](/plugins/framer-components), and a faster path if the effect fits your project but hand-tuning the geometry isn't the best use of your time on a given build.

## Where This Effect Works Best

- **Product or portfolio showcases** where a rotating ring of items functions as an interactive gallery rather than a static grid.
- **Brand or "as seen in" moments** wanting more visual weight and dimensionality than a flat logo marquee.
- **Team or feature showcases**, where each ring position represents a distinct person or capability a visitor can browse through.

It's a poor fit for content-dense pages where visitors need to compare many items simultaneously — a ring only shows one or two items clearly at a time (the front-facing ones), unlike a grid that shows everything at once. Reserve it for a smaller, curated set (6-10 items) where the showcase format's visual impact outweighs the tradeoff of reduced simultaneous visibility.

## Performance Notes

CSS 3D transforms are GPU-accelerated in all modern browsers, so the rotation itself is cheap regardless of item count within reason. The actual performance risk is what's inside each ring item — high-resolution, uncompressed images loaded for every position in the ring simultaneously, even the ones currently facing away from the viewer, add real load weight. Compress and appropriately size images before they go into the ring, and consider lazy-loading images for ring positions beyond what's initially visible if your item count is large.

## Common Mistakes

- **Setting perspective on the wrong element.** As covered above, it belongs on the outer, non-rotating container — placed on the rotating ring itself, the 3D depth effect either won't render or will look distorted in unpredictable ways.
- **Forgetting transform-style: preserve-3d on the rotating parent.** Without it, every child item's individual rotation and depth transform gets flattened into the parent's own 2D plane, collapsing the whole ring into an overlapping flat stack instead of a circle.
- **Using a radius that's too small for the item size.** This causes adjacent items to visibly overlap or clip into each other at their edges — always sanity-check the radius formula's output against your actual item size before finalizing.
- **No drag momentum or spring smoothing.** A ring that snaps instantly to follow raw drag input feels mechanical and cheap; the spring-smoothed rotation in the snippet above is what gives the interaction its premium, weighted feel.

If you're building a full showcase section and want other 3D or depth-forward pieces to pair with the ring, FramerHub's [component library](/components) has additional gallery and reveal effects worth browsing through the [Components plugin](/plugins/framer-components) before committing to a fully custom build.

## FAQ

**How do you arrange items in a 3D circle with CSS?**
Each item is rotated around the Y-axis by an even fraction of 360 degrees (360 divided by the item count), then translated outward along the Z-axis by the ring's radius, all inside a parent with perspective and transform-style: preserve-3d set.

**What radius should I use for a circular showcase?**
The radius needs to scale with both the item count and each item's width — too small a radius and items overlap or clip into each other; too large and they spread too far apart to read as one cohesive ring. As a starting point, radius roughly equal to item width divided by (2 × tan(180° / item count)) keeps adjacent items just touching.

**Should a 3D showcase rotate automatically or only on interaction?**
A slow automatic rotation with drag-to-override works well for a passive hero moment, while a fully drag/click-driven version (no automatic motion) suits a showcase where visitors are expected to actively browse specific items rather than watch it idle.

**Does a 3D circular showcase work well on mobile?**
It can, but touch-drag needs to be implemented deliberately (not just mouse events), and the effect generally needs a smaller item count and radius on narrow viewports, where there's less visual room for depth to read clearly.

**Is CSS 3D transform performance a concern for this effect?**
Generally no for a reasonable item count (6-10 items) — CSS 3D transforms are GPU-accelerated in modern browsers. Performance risk mostly comes from what's inside each item (heavy images, video) rather than the rotation itself.
