EmpireUI
Get Pro
← Blog8 min read#gradient#card#react

Gradient Card Design in React: Mesh, Conic and Radial Approaches

Build stunning gradient cards in React using mesh, conic, and radial CSS techniques. Practical code, zero dependencies, and real performance trade-offs explained.

Colorful gradient card design on a dark background with vibrant hues

Why Gradient Cards Are Worth Getting Right

Cards are everywhere. Every dashboard, every pricing page, every marketing site has them — and most look exactly the same: white fill, 1px border, 8px radius. That's fine. But if you're building something that needs to feel premium, a well-crafted gradient card does more visual work in 200px than a dozen animations could.

Honestly, the gap between a mediocre gradient card and a good one comes down to understanding *which type of gradient* to reach for. Linear gradients are dead simple and mostly fine. But mesh gradients, conic gradients, and composed radial stops unlock a completely different tier of visual quality — one that's been standard in Figma and Sketch mockups since 2023 but still rarely makes it into production React apps.

This guide skips the basics. You already know background: linear-gradient(135deg, #667eea, #764ba2). We're going deeper — mesh backgrounds via layered radials, true conic gradients for that color-wheel glow effect, and composited radial stops for depth-heavy card designs. All with zero runtime dependencies, just CSS and React.

Quick aside: if you want pre-built components to start from, browse components at Empire UI — there are gradient card variants across every visual style in the library.

Radial Gradients: More Depth Than You'd Expect

A single radial gradient on a card is a 2018 move. But *stacking* them with different origins, sizes, and stop positions? That's where it gets interesting. The trick is using multiple background-image layers with radial-gradient() calls separated by commas.

Here's a base card component that uses three radial layers to create that warm, deep glow look you see on a lot of SaaS landing pages in 2026: ``tsx const GlowCard = ({ children }: { children: React.ReactNode }) => ( <div style={{ background: radial-gradient(ellipse at 20% 20%, rgba(120, 80, 255, 0.35) 0%, transparent 60%), radial-gradient(ellipse at 80% 80%, rgba(255, 80, 120, 0.3) 0%, transparent 55%), radial-gradient(ellipse at 50% 50%, rgba(10, 10, 30, 1) 40%, rgba(5, 5, 20, 1) 100%) , borderRadius: '16px', border: '1px solid rgba(255,255,255,0.08)', padding: '32px', }} > {children} </div> ); `` That third layer is a solid dark fill — it's your actual background color. The first two are pure color stops that bleed into transparent, so they layer over anything beneath them without completely filling the card.

In practice, the at X% Y% syntax is what gives you control. at 20% 20% pushes the center of that radial to the top-left. Want the glow on the bottom-right corner? at 80% 80%. You can also drive these values from state or props to create hover effects that feel almost physically reactive to the mouse.

One more thing — keep your intermediate stops rgba(..., 0) not just transparent. Old WebKit has a known bug where transparent interpolates through black instead of the source color, leaving a dirty gray ring around your glows. Chrome fixed this in 2021 but Safari didn't land it until version 16.4.

Worth noting: radial gradients are fully GPU-composited, so stacking 3–4 of them has essentially zero layout impact. Don't be scared to add another layer for more dimension.

Conic Gradients: The Underused Tool

Most devs hit conic-gradient once, build a pie chart, and never touch it again. Which is a shame, because the color-wheel sweep it produces is one of the richest card backgrounds you can generate with pure CSS.

The key insight: you don't want a full 360° sweep on a card. You want a *partial* sweep, heavily feathered, used as a subtle border glow or a background texture. Here's a pattern that creates a rainbow-shimmer border effect — the kind that looks like it took hours in Figma: ``tsx const ConicCard = ({ children }: { children: React.ReactNode }) => ( <div style={{ position: 'relative', borderRadius: '20px', padding: '2px' }}> {/* Conic gradient border layer */} <div style={{ position: 'absolute', inset: 0, borderRadius: '20px', background: 'conic-gradient(from 180deg at 50% 50%, #ff6b6b 0deg, #ffd93d 72deg, #6bcb77 144deg, #4d96ff 216deg, #c77dff 288deg, #ff6b6b 360deg)', zIndex: 0, }} /> {/* Card content sits on top */} <div style={{ position: 'relative', zIndex: 1, background: '#0d0d1a', borderRadius: '18px', padding: '28px', }} > {children} </div> </div> ); ` The 2px padding on the wrapper becomes your border — but instead of a flat color, it's a full conic sweep. You can animate the from angle with a CSS custom property to get a spinning holo-border effect with a single @keyframes` rule.

Here's the animation version using a CSS variable driven from a React inline style: ``tsx const AnimatedConicCard = ({ children }: { children: React.ReactNode }) => { const [angle, setAngle] = React.useState(0); React.useEffect(() => { let raf: number; const tick = () => { setAngle(a => (a + 0.4) % 360); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, []); return ( <div style={{ position: 'relative', borderRadius: '20px', padding: '2px' }}> <div style={{ position: 'absolute', inset: 0, borderRadius: '20px', background: conic-gradient(from ${angle}deg at 50% 50%, #ff6b6b, #ffd93d, #6bcb77, #4d96ff, #c77dff, #ff6b6b), }} /> <div style={{ position: 'relative', background: '#0d0d1a', borderRadius: '18px', padding: '28px', zIndex: 1 }}> {children} </div> </div> ); }; ` Fair warning: updating angle in a requestAnimationFrame loop via React state causes a re-render per frame. For a single card it's fine. For a list of 20? Move the animation to a pure CSS @keyframes with a @property registration for the --angle` custom property instead.

Look, conic-gradient browser support landed in Firefox 83 and Chrome 69, so you're not going to hit compatibility issues in 2026 unless you're specifically targeting IE11 (in which case, I'm sorry). Safari has had it since 12.1.

Mesh Gradients in CSS: The Layered Radial Trick

True mesh gradients — the kind Figma and Adobe Illustrator produce with a grid of control points — don't exist natively in CSS. What you can do is approximate them well enough that nobody will notice. The technique is layering 5–8 radial gradients at different positions, each bleeding into transparent, over a base fill color.

This is the same approach design tools use under the hood when they export mesh gradients to SVG. CSS just gives you less control over the bezier curves that connect the color stops, so you're working with ellipses instead of freeform paths. In practice, the results are indistinguishable for card backgrounds at normal viewing sizes. ``tsx const MeshCard = ({ children }: { children: React.ReactNode }) => ( <div style={{ background: radial-gradient(at 0% 0%, hsla(253,100%,75%,0.5) 0px, transparent 50%), radial-gradient(at 98% 1%, hsla(340,100%,76%,0.4) 0px, transparent 50%), radial-gradient(at 50% 50%, hsla(197,100%,64%,0.35) 0px, transparent 60%), radial-gradient(at 0% 100%, hsla(22,100%,77%,0.45) 0px, transparent 50%), radial-gradient(at 80% 100%, hsla(242,100%,70%,0.4) 0px, transparent 50%), radial-gradient(at 0% 50%, hsla(355,100%,93%,0.25) 0px, transparent 50%), hsl(220,60%,12%) , borderRadius: '24px', padding: '40px', border: '1px solid rgba(255,255,255,0.1)', }} > {children} </div> ); ` That final hsl(220,60%,12%)` at the bottom is a plain color fallback — it's what you see if somehow all the gradients fail to render, but more importantly it sets the base tone that the radials blend over.

One trick worth knowing: use hsla instead of rgba for mesh layers. The hue-based syntax makes it much easier to tune color harmony quickly. If your design has a purple-to-teal theme, you just set the H values to 270 and 180 and tweak the lightness/alpha until it looks right. No hex math needed.

Want a lighter, pastel mesh? Drop the saturation to 60–80% and the alpha on each stop to 0.2–0.3. That's the vibe behind a lot of the card designs in the glassmorphism components section — a mesh background underneath frosted glass elements creates incredible depth.

For production work, I'd recommend extracting the gradient string into a theme config or a set of named presets. Hardcoding 8 rgba values inside JSX is fine for prototyping but becomes unmaintainable if your design changes. A simple object map of { warm: '...', cool: '...', neon: '...' } goes a long way.

Building a Composable GradientCard Component

Now let's wire it together. Here's a composable GradientCard that accepts a variant prop and renders the appropriate gradient background. This is the kind of thing you'd ship in a design system — it supports all three gradient types and exposes enough props to stay flexible without becoming unmanageable. ``tsx type GradientVariant = 'radial' | 'conic' | 'mesh'; const GRADIENTS: Record<GradientVariant, string> = { radial: radial-gradient(ellipse at 25% 25%, rgba(120, 80, 255, 0.4) 0%, transparent 60%), radial-gradient(ellipse at 75% 75%, rgba(255, 80, 120, 0.35) 0%, transparent 55%), hsl(230, 50%, 8%) , conic: conic-gradient(from 225deg at 50% 50%, #ff6b6b 0deg, #ffd93d 90deg, #4d96ff 180deg, #c77dff 270deg, #ff6b6b 360deg), mesh: radial-gradient(at 0% 0%, hsla(253,100%,75%,0.5) 0px, transparent 50%), radial-gradient(at 100% 0%, hsla(340,100%,76%,0.4) 0px, transparent 50%), radial-gradient(at 50% 50%, hsla(197,100%,64%,0.3) 0px, transparent 60%), radial-gradient(at 0% 100%, hsla(22,100%,77%,0.4) 0px, transparent 50%), radial-gradient(at 100% 100%, hsla(242,100%,70%,0.35) 0px, transparent 50%), hsl(220, 60%, 10%) , }; interface GradientCardProps { variant?: GradientVariant; className?: string; style?: React.CSSProperties; children: React.ReactNode; } const GradientCard = ({ variant = 'mesh', className, style, children, }: GradientCardProps) => ( <div className={className} style={{ background: GRADIENTS[variant], borderRadius: '20px', border: '1px solid rgba(255, 255, 255, 0.08)', padding: '32px', ...style, }} > {children} </div> ); export default GradientCard; ` Usage is exactly what you'd expect: <GradientCard variant="mesh">...</GradientCard>`. The conic variant looks especially striking on pricing cards. Radial is better for content cards where you want the gradient to feel like ambient lighting rather than the main visual.

That said, don't sleep on the style override prop. Designers will always want to tweak one card differently from the rest of the grid, and a style escape hatch saves you from creating a new variant every time. Keep the variants for the 80% case.

If you're using Tailwind, you can slot the gradient string into a bg-[...] arbitrary value class, but it gets ugly fast with multi-line strings. CSS modules or inline styles are honestly cleaner for gradient-heavy components. This is one of those cases where Tailwind's bg-gradient-to-br shorthand isn't expressive enough.

Check out the gradient generator to prototype your color stops visually before hardcoding them — it's faster than tweaking rgba values by hand and reloading.

Performance, Accessibility, and Dark Mode

Gradient rendering is almost entirely GPU work, so you're not going to tank your CPU with a few cards. The browser paints gradients on the compositor thread, meaning they don't block the main thread. The one exception: if you're animating gradient stops directly (not transform or opacity), you force a repaint on every frame. That's expensive. Always prefer animating a wrapper's transform: rotate() or using @property to tween a custom property, rather than swapping gradient values in JavaScript.

On accessibility: gradients themselves are fine. The concern is text contrast over gradient backgrounds. A radial that goes from 30% opacity purple to transparent means the contrast ratio changes across the card surface — which WCAG 2.2 doesn't handle well because it assumes a single background color. The safe move is to add a subtle overlay when you place text: background: rgba(0,0,0,0.25) on the text container gives you a consistent dark base that passes contrast checks at any gradient combination.

Dark mode is basically free if you design your gradients for dark backgrounds to begin with. Mesh gradients over dark fills look great in both light and dark system themes because the base fill is already dark. If you need a light variant, you'd flip to pastel mesh colors — saturation around 40–60%, lightness 85–95%, alpha around 0.3 — over a near-white base. The structure of the component stays identical.

One more thing — on Safari, you'll occasionally see gradient banding on large card surfaces. The fix is adding image-rendering: -webkit-optimize-contrast or forcing subpixel dithering via a 0.001px blur on the gradient element. It's a 2-line CSS addition that makes a noticeable visual difference on retina displays.

When to Use Each Gradient Type

This is the decision you need to make before writing any code. Not every card needs a mesh — sometimes a single radial or a conic border is the right call.

Use radial gradients when you want directional ambient lighting — a card that looks like it's being lit from one corner. Great for feature cards, testimonial cards, anything where the gradient is background atmosphere. Keep it to 2–3 layers maximum or the colors start fighting each other.

Use conic gradients for borders and halos. The full-spectrum sweep is too busy as a card fill for most designs, but as a 2px border wrapper or a bottom-edge glow, it reads as premium without overwhelming the content. Works especially well on dark backgrounds, which describes most of the cyberpunk and vaporwave card designs you'll find if you're exploring different visual directions.

Use mesh gradients when you want the card itself to be the hero — pricing sections, hero feature cards, empty states that need to feel intentional. They're the highest visual impact of the three but also the most opinionated. A mesh card in a data table looks wrong. A mesh card on a landing page feature block looks exactly right.

Honestly, combining all three in one component is usually overkill. Pick the approach that matches the role of the card in the layout, build it once, and reuse it consistently. That consistency is what makes a design system feel designed rather than assembled.

FAQ

Does conic-gradient work in all modern browsers?

Yes. Chrome 69, Firefox 83, and Safari 12.1 all support it natively. In 2026 you'd only hit issues in IE11 or old Android WebViews — both of which you can safely ignore for most projects.

Can I animate mesh gradients without killing performance?

Avoid animating the gradient stops directly in JavaScript — that forces a repaint per frame. Instead, animate a transform on the gradient layer or use a registered CSS @property to tween a custom angle or position value; the browser can handle that on the compositor thread.

How do I make gradient cards accessible for users with reduced motion preferences?

Wrap any gradient animations in @media (prefers-reduced-motion: reduce) and replace them with static gradient states. The static mesh or radial background still looks great — you're just removing the motion.

What's the difference between mesh gradient in Figma vs CSS?

Figma uses a real mesh with bezier-controlled color points, CSS doesn't have that primitive. The layered radial-gradient technique approximates it with ellipses — close enough for card backgrounds at normal screen sizes, though you'd never use it for a logo or icon.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Spotlight Card in React: Cursor-Tracking Radial Highlight EffectVaporwave Card Design: Pastel Gradients, Grid Lines and ChromeSpotlight Mouse Tracking Effect in React: Radial Gradient CursorBest CSS Animation Libraries in 2026: Motion, GSAP, Auto-Animate