> Markdown version of [/magazine/656-creating-a-3d-card-fan-with-css-transforms](https://www.wearedevelopers.com/magazine/656-creating-a-3d-card-fan-with-css-transforms). Every page supports `.md` or `Accept: text/markdown`. Links point to the HTML versions so they work for humans too. Agent guide: [/agents.md](https://www.wearedevelopers.com/agents.md). --- # Creating a 3D Card Fan with CSS Transforms **By:** [Benjamin Ruschin](https://www.wearedevelopers.com/@benjamin-ruschin) **Published:** November 21, 2025 So many of us developers use CSS every day, but the chances are we only use a very small portion of its features, and overlook many of its most fun ones. You only need cruise Codepen to find some mind-bendingly impressive CSS-based projects using 3D environments, borrowing techniques from camera work and motion graphics and elsewhere, but it’s not necessarily a great place to actually learn how they work. On a recent LIVE show we took a look at the amazing [Scope Creep](https://scope-creep.xyz/play) and it gave us the inspiration to play around with some CSS to try and achieve something similar. We’re going to [create this 3D card fan](https://github.com/WeAreDevelopers-com/3d-card-fan) with CSS transforms, perspective, and animation concepts coming together to create a component that looks nice and doesn’t use a single package or line of JavaScript. You can get the code for this project over on [the GitHub repo](https://github.com/WeAreDevelopers-com/3d-card-fan). In this article, we’ll skip over [the HTML](https://github.com/WeAreDevelopers-com/3d-card-fan/blob/main/index.html) so we can focus on the CSS, but essentially inside of our `.card-fan` we have a series of `article` elements with class `.card`. ## Basic Styles In terms of styling, let’s start of by creating the `/css` folder and a `base.css` file to handle global resets, page layout, and header styling. Because they’re not really central to this article, we’ll skip over these, but we set a nice radial gradient on the background, and some styles for our header text. Then, the `cards.css`, is where we’ll add CSS transforms to create the subtly three-dimensional fan of cards. ## Creating the 3D Environment 3D transforms in CSS are a lot of fun, but they can take a little while to get used to, especially if you don’t work with any kind of photography, videography, motion graphics or art and design. CSS 3D transforms borrow concepts from photography and art. Just as a photographer sets up a camera to view a scene, we establish a perspective: .card-fan { perspective: 1200px; perspective-origin: center center; } Here, you can see the difference when we change it to 300px: ![3d hover](https://wearedevelopers.imgix.net//magazine/articles/656/images/content/kKETZZ0PiM8E805BivDr-1763730736.gif) The `perspective` sets the camera distance, which can be a little confusing. Larger values create subtle depth, smaller values create dramatic 3D effects. Secondly, `perspective-origin` is crucial, it defines the vanishing point, where lines meet. Without it, the perspective effect can be imperceptible or misaligned. Next, we enable 3D transformations: .card { transform-style: preserve-3d; transform-origin: bottom center; } The `transform-style: preserve-3d` maintains 3D positioning for child elements, a bit like turning layers (and their children) to 3D layers in motion graphics software. The `transform-origin` acts like an anchor point—where the card pivots. Setting it to `bottom center` makes cards rotate from their base, like real playing cards. ## Working with Three Axes When we hover over our cards, we’re going to want them to move towards the viewer (rather than just scaling it up), but keeping to the `perspective` of the entire component. In order to do this, we’re going to use the third axis, the Z-axis. Rather than just moving our card horizontal on X-axis, or vertical on the Y-axis, we’ll move it closer to the viewer on the Z-axis. ## Dynamic Positioning with CSS Variables To position our cards along the path, spread out in an array, we can use the `--fan-rotation-step` CSS variable, then use this in a calculation later on. :root { --fan-rotation-step: 4deg; } .card { --index: 0; --offset-y: 0px; } Each card also gets an `--index` (-3 to 3 for 7 cards) and `--offset-y` that creates the semicircle arc, and the `--fan-rotation-step` spreads the cards. This will mean we can automatically set the rotation of each card, using `rotateZ` and `calc` to create the fan effect: transform: rotateZ(calc(var(--index) * var(--fan-rotation-step))) translateY(var(--offset-y)) translateZ(0px); The `translateY()` moves the cards vertically, with the `:nth-child()` targeting them individually to move the cards in the center, upwards. Then, the `translateZ(0px)` sets the initial depth of all of the cards. ## Moving Cards in 3D Space Finally, to create the animation, all we need to do is adjust the positioning along the Z axis with `transform`: .card:hover { transform: rotateZ(calc(var(--index) * var(--fan-rotation-step))) translateY( calc(var(--offset-y) - 30px) ) translateZ(60px); z-index: 10; transition: transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1), box-shadow 260ms cubic-bezier(0.22, 0.61, 0.36, 1), z-index 0s; } The `translateZ(60px)` moves the card closer to the camera. Combined with `perspective`, this creates depth. The card appears to lift off the table, and the `z-index` switch ensure the card you’ve hovered over comes to the front of the pack. The same `rotateZ()` calculation preserves the rotation while moving forward, and we’ve added a delay to make it’s transition back into the pack smooth when we stop hovering. ## Perspective and Depth The relationship between `perspective` and `translateZ` determines how dramatic the depth effect appears. With `perspective: 1200px` and `translateZ(60px)`, the card moves forward noticeably. The same `translateZ` with `perspective: 600px` creates a more dramatic effect—the card appears to move twice as far relative to the viewing distance. This mirrors photography, because a wide-angle lens (with a smaller perspective) exaggerates depth, while a telephoto lens (larger perspective) compresses it. ## Tweaking the Effect You can fine-tune the effect by adjusting `perspective` (depth), `perspective-origin` (vanishing point), `--fan-rotation-step` (spread), `translateZ` on hover (lift), and the `--offset-y` values (the bend of the arc). Admittedly, it can be quite complicated to predict the result you’re going to get when you’re tweaking these values, at least until you understand how it all works, but like most aspects of coding, it’s not as complicated as it seems as first. ## The Final Product There we go! The final product is complete, and ready to tweak as you wish. ![3d hover final](https://wearedevelopers.imgix.net//magazine/articles/656/images/content/HkyZeYMZEU7tslg2YUzP-1763730736.gif) * * * In case you missed it earlier in the article, you can find all of the code featured here [over on our GitHub](https://github.com/WeAreDevelopers-com/3d-card-fan/blob/main/css/cards.css), and be sure to let us know your thoughts over on socials. ## Related Articles - [Building a "shoutout" component in plain HTML/CSS/JavaScript](https://www.wearedevelopers.com/magazine/556-building-a-shoutout-component-in-plain-html-css-javascript) - [CSS-Only Scroll-Driven Animations](https://www.wearedevelopers.com/magazine/712-css-only-scroll-driven-animations) - [Using light-dark() for Easy Theme Switching](https://www.wearedevelopers.com/magazine/630-using-light-dark-for-easy-theme-switching) - [Creating a Spotlight Text Effect with CSS and JavaScript](https://www.wearedevelopers.com/magazine/637-creating-a-spotlight-text-effect-with-css-and-javascript) ## Related Videos - [Playing Games with CSS](https://www.wearedevelopers.com/videos/210-playing-games-with-css) - [WeAreDevelopers LIVE - CSS is DOOMed](https://www.wearedevelopers.com/videos/1851-wearedevelopers-live-css-is-doomed) - [Can CSS recreate Tufte's designs?](https://www.wearedevelopers.com/videos/517-can-css-recreate-tufte-s-designs) - [WeAreDevelopers LIVE - CSS or BS?](https://www.wearedevelopers.com/videos/1850-wearedevelopers-live-css-or-bs) ## Related Jobs - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/639235-remote-senior-full-stack-engineer) at **Edge Impulse** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/679408-remote-senior-full-stack-engineer) at **Edge Impulse** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/682327-remote-senior-full-stack-engineer) at **Edge Impulse** - [Senior Software Engineer, React (Websites & Martech)](https://www.wearedevelopers.com/jobs/ext/1605272-senior-software-engineer-react-websites-martech) at **Bitpanda** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/643147-remote-senior-full-stack-engineer) at **Edge Impulse** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/683045-remote-senior-full-stack-engineer) at **Edge Impulse**