Default mesh
Five tent-ui-tinted radial gradients drift behind the content. Animation pauses for users with prefers-reduced-motion.
Custom palette
Pass any 3–5 CSS colors and tune speed / blur to taste — no shader, no WebGL.
Installation
npx shadcn@latest add https://tentui.com/r/mesh-gradient-card.json
Usage
MeshGradientCard is a styled <div> — drop any content inside. The mesh is rendered behind the children, so they remain crisp and selectable.
import { MeshGradientCard } from "@/components/mesh-gradient-card";
export function FeatureTile() {
return (
<MeshGradientCard className="min-h-56">
<div className="p-6">
<h3 className="text-base font-semibold">Ambient</h3>
<p className="mt-1 text-sm text-muted-foreground">
A soft, drifting mesh that won't fight your typography.
</p>
</div>
</MeshGradientCard>
);
}Patterns
Custom palette
Pass 3–5 CSS colors. OKLCH is recommended for predictable lightness across the wheel, but any CSS color works — including translucent values.
<MeshGradientCard
colors={[
"oklch(0.78 0.16 200)",
"oklch(0.72 0.18 160)",
"oklch(0.82 0.14 230)",
"oklch(0.7 0.2 280)"
]}
>
{children}
</MeshGradientCard>Calmer drift
Set speed below 1 for a slower, more ambient feel — useful as a hero background or behind long-form copy.
<MeshGradientCard speed={0.4} blur={120}>{children}</MeshGradientCard>Crisper mesh
Drop blur to keep more shape definition (useful at small sizes), or raise grain opacity by disabling and re-applying your own overlay.
<MeshGradientCard blur={40} grain={false}>{children}</MeshGradientCard>Props
| Prop | Type | Default | Description |
|---|---|---|---|
colors | string[] | tent-ui palette | 3–5 CSS colors that compose the mesh. Translucent values give softer blends. |
speed | number | 1 | Animation-speed multiplier. Higher is faster; 0 is effectively static. |
blur | number | 80 | Blur radius applied to the gradient layer, in pixels. |
grain | boolean | true | Overlay a subtle SVG noise grain on top of the mesh. |
className | string | — | Forwarded to the outer <div>. |
All other <div> props (onClick, id, style, …) pass through.
How it works
The card stacks three layers inside an overflow-hidden wrapper:
- Mesh layer. A
pointer-events-none<div>with a large CSSblur()filter. Inside, one absolutely-positioned<span>per color renders aradial-gradientblob. Each blob is wrapped in a Framer Motionmotion.spanand animatesx,y, andscalealong a per-blob path, so the gradient layer drifts without React re-rendering. - Grain layer. An inline SVG
feTurbulencenoise pattern blended withmix-blend-overlayfor a tactile, off-screen-print feel. Toggle with thegrainprop. - Content layer. Your children render in a
relativediv on top of both layers. The outer wrapper usesbg-cardso the card has a sensible theme-aware fallback when motion is disabled or the gradients haven't painted yet.
useReducedMotion from motion/react short-circuits the animation when prefers-reduced-motion: reduce is set — the blobs render in their starting frame and no requestAnimationFrame loop runs. All movement happens via GPU-accelerated transforms (x, y, scale), so a card grid of these stays smooth even on low-power devices.