Spotlight Grid

Hero-ready card grid with cursor-tracked spotlight, distance-based lift, and premium motion falloff.

Default
Apple-like hero feature grid with smooth spotlight

Realtime sync

Sub-100ms collaborative updates.

WebSocket edge mesh

Secure by default

Role policies and audit logs built in.

SOC 2 ready controls

Visual analytics

Funnels and cohort insights in one view.

No SQL required

Workflow automations

Trigger actions from events instantly.

40+ native integrations

AI copilots

Context-aware suggestions in every module.

Trained on workspace data

Global edge delivery

Fast responses across all regions.

99.99% uptime target

1"use client";
2
3import * as React from "react";
4import { cva, type VariantProps } from "class-variance-authority";
5import { cn } from "@/lib/utils";
6
7const gridVariants = cva("relative grid gap-4", {
8 variants: {
9 columns: {
10 2: "grid-cols-1 sm:grid-cols-2",
11 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
12 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
13 },
14 },
15 defaultVariants: {
16 columns: 3,
17 },
18});
19
20const cardVariants = cva(
21 "group relative overflow-hidden rounded-xl border bg-card transition-all duration-200 ease-out will-change-transform",
22 {
23 variants: {
24 variant: {
25 default: "border-border/60",
26 glow: "border-primary/30",
27 subtle: "border-border/40 bg-muted/40",
28 },
29 size: {
30 sm: "p-4",
31 md: "p-5",
32 lg: "p-6",
33 },
34 },
35 defaultVariants: {
36 variant: "default",
37 size: "md",
38 },
39 },
40);
41
42export type SpotlightGridItem = {
43 title: string;
44 description: string;
45 meta?: string;
46};
47
48interface SpotlightGridProps
49 extends VariantProps<typeof gridVariants>, VariantProps<typeof cardVariants> {
50 items: SpotlightGridItem[];
51 radius?: number;
52 intensity?: number;
53 className?: string;
54}
55
56export function SpotlightGrid({
57 items,
58 columns,
59 variant,
60 size,
61 radius = 200,
62 intensity = 0.6,
63 className,
64}: SpotlightGridProps) {
65 const cardRefs = React.useRef<(HTMLDivElement | null)[]>([]);
66 const mouse = React.useRef({ x: 0, y: 0 });
67 const raf = React.useRef<number | null>(null);
68
69 const update = React.useCallback(() => {
70 cardRefs.current.forEach((card) => {
71 if (!card) return;
72
73 const rect = card.getBoundingClientRect();
74
75 const x = mouse.current.x - rect.left;
76 const y = mouse.current.y - rect.top;
77
78 const centerX = rect.width / 2;
79 const centerY = rect.height / 2;
80
81 const dx = x - centerX;
82 const dy = y - centerY;
83
84 const dist = Math.sqrt(dx * dx + dy * dy);
85 const norm = Math.max(0, 1 - dist / radius);
86 let boost = norm * intensity;
87
88 // Slight boost for hovered card
89 if (card.matches(":hover")) {
90 boost *= 1.4;
91 }
92
93 card.style.setProperty("--x", `${x}px`);
94 card.style.setProperty("--y", `${y}px`);
95 card.style.setProperty("--boost", boost.toString());
96 });
97
98 raf.current = requestAnimationFrame(update);
99 }, [radius, intensity]);
100
101 const handleMove = (e: React.MouseEvent<HTMLDivElement>) => {
102 mouse.current.x = e.clientX;
103 mouse.current.y = e.clientY;
104
105 if (!raf.current) {
106 raf.current = requestAnimationFrame(update);
107 }
108 };
109
110 const handleLeave = () => {
111 if (raf.current) {
112 cancelAnimationFrame(raf.current);
113 raf.current = null;
114 }
115
116 cardRefs.current.forEach((card) => {
117 if (!card) return;
118 card.style.removeProperty("--boost");
119 });
120 };
121
122 React.useEffect(() => {
123 return () => {
124 if (raf.current) cancelAnimationFrame(raf.current);
125 };
126 }, []);
127
128 return (
129 <div
130 onMouseMove={handleMove}
131 onMouseLeave={handleLeave}
132 className={cn(gridVariants({ columns }), className)}
133 >
134 {items.map((item, i) => (
135 <div
136 key={`${item.title}-${i}`}
137 ref={(el) => {
138 if (el) {
139 cardRefs.current[i] = el;
140 }
141 }}
142 className={cn(cardVariants({ variant, size }))}
143 style={{
144 transform:
145 "translateY(calc(var(--boost, 0) * -6px)) scale(calc(1 + var(--boost, 0) * 0.04))",
146 boxShadow:
147 "0 20px 40px -20px rgba(0,0,0,calc(0.2 + var(--boost, 0) * 0.3))",
148 backgroundImage: `
149 radial-gradient(
150 ${radius}px circle at var(--x, 50%) var(--y, 50%),
151 rgba(255,255,255,calc(0.1 + var(--boost, 0) * 0.25)),
152 transparent 60%
153 )
154 `,
155 }}
156 >
157 {/* Glow overlay */}
158 <div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-gradient-to-b from-white/5 to-transparent" />
159
160 {/* Content */}
161 <div className="relative z-10 space-y-2">
162 <h3 className="text-sm font-semibold">{item.title}</h3>
163 <p className="text-sm text-muted-foreground">{item.description}</p>
164 {item.meta && (
165 <p className="text-xs text-muted-foreground/80">{item.meta}</p>
166 )}
167 </div>
168 </div>
169 ))}
170 </div>
171 );
172}
Subtle variant
Apple-like hero feature grid with glow variant

Realtime sync

Sub-100ms collaborative updates.

WebSocket edge mesh

Secure by default

Role policies and audit logs built in.

SOC 2 ready controls

Visual analytics

Funnels and cohort insights in one view.

No SQL required

Workflow automations

Trigger actions from events instantly.

40+ native integrations

AI copilots

Context-aware suggestions in every module.

Trained on workspace data

Global edge delivery

Fast responses across all regions.

99.99% uptime target

1"use client";
2
3import * as React from "react";
4import { cva, type VariantProps } from "class-variance-authority";
5import { cn } from "@/lib/utils";
6
7const gridVariants = cva("relative grid gap-4", {
8 variants: {
9 columns: {
10 2: "grid-cols-1 sm:grid-cols-2",
11 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
12 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
13 },
14 },
15 defaultVariants: {
16 columns: 3,
17 },
18});
19
20const cardVariants = cva(
21 "group relative overflow-hidden rounded-xl border bg-card transition-all duration-200 ease-out will-change-transform",
22 {
23 variants: {
24 variant: {
25 default: "border-border/60",
26 glow: "border-primary/30",
27 subtle: "border-border/40 bg-muted/40",
28 },
29 size: {
30 sm: "p-4",
31 md: "p-5",
32 lg: "p-6",
33 },
34 },
35 defaultVariants: {
36 variant: "default",
37 size: "md",
38 },
39 },
40);
41
42export type SpotlightGridItem = {
43 title: string;
44 description: string;
45 meta?: string;
46};
47
48interface SpotlightGridProps
49 extends VariantProps<typeof gridVariants>, VariantProps<typeof cardVariants> {
50 items: SpotlightGridItem[];
51 radius?: number;
52 intensity?: number;
53 className?: string;
54}
55
56export function SpotlightGrid({
57 items,
58 columns,
59 variant,
60 size,
61 radius = 200,
62 intensity = 0.6,
63 className,
64}: SpotlightGridProps) {
65 const cardRefs = React.useRef<(HTMLDivElement | null)[]>([]);
66 const mouse = React.useRef({ x: 0, y: 0 });
67 const raf = React.useRef<number | null>(null);
68
69 const update = React.useCallback(() => {
70 cardRefs.current.forEach((card) => {
71 if (!card) return;
72
73 const rect = card.getBoundingClientRect();
74
75 const x = mouse.current.x - rect.left;
76 const y = mouse.current.y - rect.top;
77
78 const centerX = rect.width / 2;
79 const centerY = rect.height / 2;
80
81 const dx = x - centerX;
82 const dy = y - centerY;
83
84 const dist = Math.sqrt(dx * dx + dy * dy);
85 const norm = Math.max(0, 1 - dist / radius);
86 let boost = norm * intensity;
87
88 // Slight boost for hovered card
89 if (card.matches(":hover")) {
90 boost *= 1.4;
91 }
92
93 card.style.setProperty("--x", `${x}px`);
94 card.style.setProperty("--y", `${y}px`);
95 card.style.setProperty("--boost", boost.toString());
96 });
97
98 raf.current = requestAnimationFrame(update);
99 }, [radius, intensity]);
100
101 const handleMove = (e: React.MouseEvent<HTMLDivElement>) => {
102 mouse.current.x = e.clientX;
103 mouse.current.y = e.clientY;
104
105 if (!raf.current) {
106 raf.current = requestAnimationFrame(update);
107 }
108 };
109
110 const handleLeave = () => {
111 if (raf.current) {
112 cancelAnimationFrame(raf.current);
113 raf.current = null;
114 }
115
116 cardRefs.current.forEach((card) => {
117 if (!card) return;
118 card.style.removeProperty("--boost");
119 });
120 };
121
122 React.useEffect(() => {
123 return () => {
124 if (raf.current) cancelAnimationFrame(raf.current);
125 };
126 }, []);
127
128 return (
129 <div
130 onMouseMove={handleMove}
131 onMouseLeave={handleLeave}
132 className={cn(gridVariants({ columns }), className)}
133 >
134 {items.map((item, i) => (
135 <div
136 key={`${item.title}-${i}`}
137 ref={(el) => {
138 if (el) {
139 cardRefs.current[i] = el;
140 }
141 }}
142 className={cn(cardVariants({ variant, size }))}
143 style={{
144 transform:
145 "translateY(calc(var(--boost, 0) * -6px)) scale(calc(1 + var(--boost, 0) * 0.04))",
146 boxShadow:
147 "0 20px 40px -20px rgba(0,0,0,calc(0.2 + var(--boost, 0) * 0.3))",
148 backgroundImage: `
149 radial-gradient(
150 ${radius}px circle at var(--x, 50%) var(--y, 50%),
151 rgba(255,255,255,calc(0.1 + var(--boost, 0) * 0.25)),
152 transparent 60%
153 )
154 `,
155 }}
156 >
157 {/* Glow overlay */}
158 <div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-gradient-to-b from-white/5 to-transparent" />
159
160 {/* Content */}
161 <div className="relative z-10 space-y-2">
162 <h3 className="text-sm font-semibold">{item.title}</h3>
163 <p className="text-sm text-muted-foreground">{item.description}</p>
164 {item.meta && (
165 <p className="text-xs text-muted-foreground/80">{item.meta}</p>
166 )}
167 </div>
168 </div>
169 ))}
170 </div>
171 );
172}
Glow variant
Apple-like hero feature grid with glow variant

Deploy pipeline

5 stages passing

Avg 3m 42s

Error tracking

2 unresolved issues

Last seen 4m ago

Incident SLA

99.95% this month

Target 99.9%

API latency

P95 at 126ms

Global avg

1"use client";
2
3import * as React from "react";
4import { cva, type VariantProps } from "class-variance-authority";
5import { cn } from "@/lib/utils";
6
7const gridVariants = cva("relative grid gap-4", {
8 variants: {
9 columns: {
10 2: "grid-cols-1 sm:grid-cols-2",
11 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
12 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
13 },
14 },
15 defaultVariants: {
16 columns: 3,
17 },
18});
19
20const cardVariants = cva(
21 "group relative overflow-hidden rounded-xl border bg-card transition-all duration-200 ease-out will-change-transform",
22 {
23 variants: {
24 variant: {
25 default: "border-border/60",
26 glow: "border-primary/30",
27 subtle: "border-border/40 bg-muted/40",
28 },
29 size: {
30 sm: "p-4",
31 md: "p-5",
32 lg: "p-6",
33 },
34 },
35 defaultVariants: {
36 variant: "default",
37 size: "md",
38 },
39 },
40);
41
42export type SpotlightGridItem = {
43 title: string;
44 description: string;
45 meta?: string;
46};
47
48interface SpotlightGridProps
49 extends VariantProps<typeof gridVariants>, VariantProps<typeof cardVariants> {
50 items: SpotlightGridItem[];
51 radius?: number;
52 intensity?: number;
53 className?: string;
54}
55
56export function SpotlightGrid({
57 items,
58 columns,
59 variant,
60 size,
61 radius = 200,
62 intensity = 0.6,
63 className,
64}: SpotlightGridProps) {
65 const cardRefs = React.useRef<(HTMLDivElement | null)[]>([]);
66 const mouse = React.useRef({ x: 0, y: 0 });
67 const raf = React.useRef<number | null>(null);
68
69 const update = React.useCallback(() => {
70 cardRefs.current.forEach((card) => {
71 if (!card) return;
72
73 const rect = card.getBoundingClientRect();
74
75 const x = mouse.current.x - rect.left;
76 const y = mouse.current.y - rect.top;
77
78 const centerX = rect.width / 2;
79 const centerY = rect.height / 2;
80
81 const dx = x - centerX;
82 const dy = y - centerY;
83
84 const dist = Math.sqrt(dx * dx + dy * dy);
85 const norm = Math.max(0, 1 - dist / radius);
86 let boost = norm * intensity;
87
88 // Slight boost for hovered card
89 if (card.matches(":hover")) {
90 boost *= 1.4;
91 }
92
93 card.style.setProperty("--x", `${x}px`);
94 card.style.setProperty("--y", `${y}px`);
95 card.style.setProperty("--boost", boost.toString());
96 });
97
98 raf.current = requestAnimationFrame(update);
99 }, [radius, intensity]);
100
101 const handleMove = (e: React.MouseEvent<HTMLDivElement>) => {
102 mouse.current.x = e.clientX;
103 mouse.current.y = e.clientY;
104
105 if (!raf.current) {
106 raf.current = requestAnimationFrame(update);
107 }
108 };
109
110 const handleLeave = () => {
111 if (raf.current) {
112 cancelAnimationFrame(raf.current);
113 raf.current = null;
114 }
115
116 cardRefs.current.forEach((card) => {
117 if (!card) return;
118 card.style.removeProperty("--boost");
119 });
120 };
121
122 React.useEffect(() => {
123 return () => {
124 if (raf.current) cancelAnimationFrame(raf.current);
125 };
126 }, []);
127
128 return (
129 <div
130 onMouseMove={handleMove}
131 onMouseLeave={handleLeave}
132 className={cn(gridVariants({ columns }), className)}
133 >
134 {items.map((item, i) => (
135 <div
136 key={`${item.title}-${i}`}
137 ref={(el) => {
138 if (el) {
139 cardRefs.current[i] = el;
140 }
141 }}
142 className={cn(cardVariants({ variant, size }))}
143 style={{
144 transform:
145 "translateY(calc(var(--boost, 0) * -6px)) scale(calc(1 + var(--boost, 0) * 0.04))",
146 boxShadow:
147 "0 20px 40px -20px rgba(0,0,0,calc(0.2 + var(--boost, 0) * 0.3))",
148 backgroundImage: `
149 radial-gradient(
150 ${radius}px circle at var(--x, 50%) var(--y, 50%),
151 rgba(255,255,255,calc(0.1 + var(--boost, 0) * 0.25)),
152 transparent 60%
153 )
154 `,
155 }}
156 >
157 {/* Glow overlay */}
158 <div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-gradient-to-b from-white/5 to-transparent" />
159
160 {/* Content */}
161 <div className="relative z-10 space-y-2">
162 <h3 className="text-sm font-semibold">{item.title}</h3>
163 <p className="text-sm text-muted-foreground">{item.description}</p>
164 {item.meta && (
165 <p className="text-xs text-muted-foreground/80">{item.meta}</p>
166 )}
167 </div>
168 </div>
169 ))}
170 </div>
171 );
172}

Props

NameTypeDefaultDescription
items{ title: string; description: string; meta?: string }[]requiredGrid cards displayed with spotlight interaction
intensitynumber0.6Overall strength of brighten/lift effect
radiusnumber250Spotlight influence radius in pixels
falloff (CVA)"smooth" | "linear" | "sharp""smooth"Curve for how quickly cards fade from spotlight
columns (CVA)"2" | "3" | "4""3"Responsive column preset for card layout