- Published on
How to create a digital coaster
- Authors
- Name
- Matt Morris

Aside from my digital coaster collection itself, which you can view in my blog post here, I wanted to make a separate post covering the technologies and techniques I used to create it.
The idea was to create a digital approximation of holding a coaster in your hand, with the ability to flip it over and see the other side in a natural feeling way, instead of simply changing the image abruptly on-click.
As one of my first web development practice projects, it has existed in some form for several years before I finally published it here, and transformed along the way from simple HTML/CSS with JQuery, to various React packages, and finally to Framer Motion which I currently use to create the flip animation.
Here's how I did it:
The images

I started out with a stack of coasters and the idea to turn them into a digital collection. Using my cutting board as a neutral background, I took front and back pictures of each coaster.
Using digital editing tools (first Photoshop, later Figma) I created a shape to use as a mask for cropping the image out of its background. For circular coasters this was relatively easy but for other irregular shapes, like this Aslan Brewing coaster, it was more challenging as I had to use the pen tool to create a custom shape.


The code
The top layer of the code is JSX (which stands for Javascript XML), React's way to allow users to write HTML code in a relatively natural way that works within the React library. This is held in an MDX file (the markdown file format used within React).
JSX/HTML
<div className="coaster-container">
<div className="sub-coaster-container hoverWrapper">
<FlippingCard
className="coaster"
frontImage="/static/images/coasters/balefront.png"
backImage="/static/images/coasters/baleback.png"
/>
<div className="hoverShow">
<div>Bale Breaker Brewing Company</div>
<div>Yakima, Washington</div>
</div>
</div>
</div>
CSS
I use CSS for the positioning, text styling, and the animated company title/location block that appears underneath the coasters on-hover:
/* Default coaster styles */
.coaster-container {
text-align: center;
margin-top: 30px;
vertical-align: top;
}
.sub-coaster-container {
text-align: center;
display: inline-block;
margin-right: 50px;
vertical-align: top;
}
@media (max-width: 767px) {
.sub-coaster-container {
margin-right: 20px;
}
}
.hoverWrapper:hover .hoverShow {
opacity: 1;
visibility: visible;
margin-bottom: 50px;
}
.hoverWrapper .hoverShow {
opacity: 0;
visibility: hidden;
text-align: center;
line-height: 16pt;
font-size: 9pt;
font-family: sans-serif;
margin-top: 50px;
margin-bottom: 0;
transition:
margin-bottom 0.3s ease,
opacity 0.2s ease,
visibility 0.3s ease;
}
React component (TypeScript)
I created a React component to import the Framer Motion library and program the 'flip' effect on the edited coaster images. If you refer back to the JSX/HTML code above, you can see where I pull in the FlippingCard component for each coaster.
'use client'
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import Image from 'next/image'
const FlippingCard = ({ frontImage, backImage, width = 200, height = 200 }) => {
const [isFlipped, setIsFlipped] = useState(false)
const handleClick = () => {
setIsFlipped(!isFlipped)
}
const handleKeyDown = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
setIsFlipped(!isFlipped)
}
}
return (
<div
onClick={handleClick}
onKeyDown={handleKeyDown}
role="button"
tabIndex={0}
style={{
perspective: '1000px',
position: 'relative',
width: `${width}px`,
height: `${height}px`,
}}
>
<motion.div
style={{
width: '100%',
height: '100%',
position: 'absolute',
transformStyle: 'preserve-3d',
transition: 'transform 0.6s',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
}}
>
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backfaceVisibility: 'hidden',
position: 'absolute',
opacity: isFlipped ? 0 : 1,
transition: 'opacity 0.6s',
}}
>
<Image src={frontImage} alt="Front" fill className="object-cover" />
</div>
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backfaceVisibility: 'hidden',
position: 'absolute',
transform: 'rotateY(180deg)',
opacity: isFlipped ? 1 : 0,
transition: 'opacity 0.6s',
}}
>
<Image src={backImage} alt="Back" fill className="object-cover" />
</div>
</motion.div>
</div>
)
}
export default FlippingCard
The result:
Voilà, a digital coaster. See the full collection here