Vaporwave Card Design: Pastel Gradients, Grid Lines and Chrome
Build vaporwave cards with CSS pastel gradients, retro grid lines, chrome text, and scanlines — no libraries needed, just vibes and good taste.
What Vaporwave Actually Looks Like (and Why It Still Works)
Vaporwave isn't just a playlist aesthetic from 2012. It's a specific visual language — hot pink into cyan, perspective grids receding into a dark horizon, chrome letterforms catching light that doesn't exist in nature. Cards are one of the best containers for this stuff because their bounded rectangle gives the busy palette somewhere to breathe.
If you've browsed the vaporwave style hub, you'll know the core ingredients: a dark base (usually near-black purple or midnight navy), pastel gradients that run diagonal or radial, and at least one element that looks like it was extruded from a 1988 CGI render. Grid lines and scanlines are optional but add a lot with almost no code.
In practice, you don't need to throw all of these at a single card. Pick two or three. A card with a pastel gradient background and chrome text is already unmistakably vaporwave — you don't also need the grid, the scanlines, and a rotating 3D bust. More restraint, more impact.
Worth noting: browsers have fully supported conic-gradient since Chrome 69 (2018), backdrop-filter since Chrome 76, and CSS custom properties forever. Everything in this article is pure CSS and React — no canvas, no WebGL, no weird polyfills.
The Base Card: Dark Purple, Pastel Gradient, Border Glow
Start with the container. You want a deep background — something like #0d0015 or #0a0a1a — and a gradient overlay that runs from hot pink through violet to cyan. The card border is where the glow lives. A 1px border with box-shadow in the same gradient range sells the neon-lit-from-within feeling.
.vaporwave-card {
--pink: #ff6ec7;
--violet: #a855f7;
--cyan: #06b6d4;
position: relative;
background: linear-gradient(135deg, #0d0015 0%, #120025 100%);
border: 1px solid rgba(168, 85, 247, 0.4);
border-radius: 12px;
padding: 32px;
box-shadow:
0 0 0 1px rgba(255, 110, 199, 0.15),
0 0 24px rgba(168, 85, 247, 0.35),
0 0 60px rgba(6, 182, 212, 0.1);
}The triple box-shadow trick — tight inner ring, mid-range violet bloom, far cyan haze — is how you get that layered neon glow without an SVG filter. You can tune the third value. At 60px it's atmospheric. At 120px it starts eating adjacent UI. Use the box shadow generator if you want to preview live before committing values.
One more thing — add overflow: hidden to the card. You'll be layering pseudo-elements inside it and you don't want them bleeding out on smaller screens.
Pastel Gradient Overlays Without Making It Look Muddy
Honestly, most vaporwave card attempts go wrong here. People stack too many gradient layers and end up with brown. The trick is to keep your gradients semi-transparent and composited with mix-blend-mode. A radial-gradient centered top-left in hot pink at 20% opacity, mixed with screen, will tint your dark base without destroying the darkness.
.vaporwave-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background:
radial-gradient(ellipse at 10% 20%, rgba(255, 110, 199, 0.25) 0%, transparent 60%),
radial-gradient(ellipse at 90% 80%, rgba(6, 182, 212, 0.2) 0%, transparent 60%);
mix-blend-mode: screen;
pointer-events: none;
}Two radial gradients — one pink top-left, one cyan bottom-right — and mix-blend-mode: screen is all you need. The blend mode adds light rather than mixing pigment, so your colors stay vibrant against the dark base. This is exactly how the gradient generator previews multi-stop gradients if you set a dark background layer.
For the React component pattern, pass these color variables as props so you can theme variants without duplicating CSS:
type VaporwaveCardProps = {
children: React.ReactNode;
accentPink?: string;
accentCyan?: string;
className?: string;
};
export function VaporwaveCard({
children,
accentPink = '#ff6ec7',
accentCyan = '#06b6d4',
className = '',
}: VaporwaveCardProps) {
return (
<div
className={`vaporwave-card ${className}`}
style={{
'--pink': accentPink,
'--cyan': accentCyan,
} as React.CSSProperties}
>
{children}
</div>
);
}Retro Grid Lines: The CSS-Only Perspective Grid
The receding grid is vaporwave's most iconic element. And yes, you can do it entirely in CSS — no SVG, no canvas. The technique uses a second pseudo-element with a repeating-linear-gradient for the horizontal bars and a separate gradient for the verticals, then applies perspective and rotateX to fake the vanishing point.
.vaporwave-card__grid {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 50%;
border-radius: 0 0 12px 12px;
overflow: hidden;
transform-style: preserve-3d;
pointer-events: none;
}
.vaporwave-card__grid::before {
content: '';
position: absolute;
inset: -20% -50%;
background-image:
repeating-linear-gradient(
transparent,
transparent 19px,
rgba(168, 85, 247, 0.35) 20px
),
repeating-linear-gradient(
90deg,
transparent,
transparent 39px,
rgba(168, 85, 247, 0.35) 40px
);
transform: perspective(200px) rotateX(30deg);
transform-origin: bottom center;
}The inset: -20% -50% expansion is important. When you rotate a box in perspective, the visible area shrinks — you need the element to be larger than its container before the transform so grid lines cover the full floor. Adjust the 40px column width and 20px row height to taste. At 40px x 20px you get that classic arcade-horizon grid density.
Look, the perspective(200px) value is very sensitive. Change it to 800px and you barely see any foreshortening. Drop it to 100px and it's almost isometric. 200px is the sweet spot for a card-sized grid. Test on mobile too — the grid can look compressed on narrow viewports and you might want to hide it below 480px.
If you want the grid to animate — that slow infinite scroll toward the viewer — it's one @keyframes call translating translateY on the pseudo-element from 0 to the row repeat interval (20px) over 2s, linear, infinite. Dead simple. Very hypnotic.
Chrome Text and Scanlines
Chrome text is a background-clip: text trick. You set a metallic gradient on the element background, clip it to the text shape, and make the color transparent. The gradient shows through the letterforms. For vaporwave chrome specifically, you want silver-to-pink-to-silver — not pure grey, that reads as plain metallic rather than synthwave-era chrome.
.vaporwave-card__title {
font-size: 2rem;
font-weight: 900;
letter-spacing: 0.05em;
text-transform: uppercase;
background: linear-gradient(
180deg,
#ffffff 0%,
#c084fc 30%,
#ff6ec7 50%,
#c084fc 70%,
#ffffff 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}Scanlines are the other signature detail. A repeating linear gradient at 2px intervals — alternating between transparent and a very low-opacity black — laid over the card creates that CRT monitor feeling. Keep the opacity at 0.03 to 0.06. Any higher and it looks like the card is broken rather than retro.
.vaporwave-card::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 1px,
rgba(0, 0, 0, 0.04) 2px,
rgba(0, 0, 0, 0.04) 3px
);
pointer-events: none;
z-index: 10;
}Quick aside: pointer-events: none on both pseudo-elements is non-negotiable. Forget it and your card content becomes unclickable because the absolutely-positioned overlay intercepts all events. It's a classic trap, especially when you're stacking three or four ::before/::after layers and a grid div.
Hover Interactions and Animation Polish
A static vaporwave card is fine. An animated one is way better. The move that actually looks good: on hover, increase the outer glow spread and shift the gradient overlay position slightly. Combined with a subtle translateY(-4px) lift, it reads as the card powering up — very on-brand.
.vaporwave-card {
transition:
transform 0.25s ease,
box-shadow 0.25s ease;
}
.vaporwave-card:hover {
transform: translateY(-4px);
box-shadow:
0 0 0 1px rgba(255, 110, 199, 0.3),
0 0 40px rgba(168, 85, 247, 0.55),
0 0 100px rgba(6, 182, 212, 0.2);
}For the animated grid, wrap the keyframes in a prefers-reduced-motion media query. Some users get motion sickness from looping animations, and an infinite-scrolling floor grid is exactly the kind of thing that triggers it.
@keyframes grid-scroll {
from { transform: perspective(200px) rotateX(30deg) translateY(0); }
to { transform: perspective(200px) rotateX(30deg) translateY(20px); }
}
@media (prefers-reduced-motion: no-preference) {
.vaporwave-card__grid::before {
animation: grid-scroll 2s linear infinite;
}
}That said, if you want Framer Motion for the card lift and spring feel, the swap is trivial — replace the CSS transition with whileHover={{ y: -4 }} on a motion.div wrapper. But for pure vaporwave cards that are mostly decorative or informational, CSS transitions perform better and ship less JS.
Putting It All Together: Full Card Component
Here's the complete component. It combines the gradient overlay pseudo-element, the perspective grid div, the scanline ::after, and the chrome title into one self-contained piece. Drop it into any Next.js or React project — no external dependencies beyond React itself.
import './vaporwave-card.css';
export function VaporwaveCard({
title,
subtitle,
tag,
children,
}: {
title: string;
subtitle?: string;
tag?: string;
children?: React.ReactNode;
}) {
return (
<div className="vaporwave-card">
<div className="vaporwave-card__grid" aria-hidden />
<div className="vaporwave-card__body">
{tag && (
<span className="vaporwave-card__tag">{tag}</span>
)}
<h2 className="vaporwave-card__title">{title}</h2>
{subtitle && (
<p className="vaporwave-card__subtitle">{subtitle}</p>
)}
{children}
</div>
</div>
);
}The aria-hidden on the grid div matters. Screen readers don't need to announce a decorative CSS grid — omitting that attribute means some assistive tools will tab into an empty element, which is confusing. Same goes for the pseudo-element scanlines.
If you want pre-built vaporwave cards without hand-rolling the CSS, the vaporwave style hub on Empire UI has copy-paste-ready components. Or browse the full component library for adjacent aesthetics — the y2k and cyberpunk hubs share some DNA with vaporwave and pair well when you're building a retro-themed product page.
FAQ
Yes, but use them selectively — a feature highlight card or a pricing callout works great. Full-page vaporwave gets exhausting fast. Treat it as an accent, not a base theme.
It's supported in every modern browser as of 2023. Keep -webkit-background-clip alongside the standard property for safety — some older WebKit builds still need it.
Drop the grid height from 50% to around 35% on screens narrower than 480px. The perspective compression looks more severe on small viewports because the card itself is shorter.
Wide, heavy sans-serifs: Space Grotesk, Bebas Neue, or Big Shoulders Display. You want something with mass for the chrome gradient to wrap around. Thin fonts lose the effect.