The future of software is here
Experience a unified platform that combines power, speed, and elegance. Built for teams that demand excellence.
Crafted for perfection
Our design system is more than just components. It's a cohesive language that bridges the gap between design and engineering, ensuring consistency at scale with over 500+ pre-built atomic components.
Installation
Copy and paste the following code into your project.
"use client";
import { useState, useEffect } from "react";
import {
motion,
AnimatePresence,
useMotionValue,
useSpring,
useTransform,
useMotionTemplate,
} from "framer-motion";
import { cn } from "@/lib/utils";
import { LucideIcon, ArrowRight } from "lucide-react";
import Image from "next/image";
interface Tab {
id: string;
label: string;
title: string;
description: string;
icon?: LucideIcon;
image: string;
cta?: {
text: string;
href?: string;
onClick?: () => void;
};
}
interface FeatureTabsProps {
badge?: string;
headline: string;
description?: string;
tabs: Tab[];
autoPlayInterval?: number;
}
export function FeatureTabs({
badge,
headline,
description,
tabs,
autoPlayInterval = 5000,
}: FeatureTabsProps) {
const [activeTabId, setActiveTabId] = useState(tabs[0].id);
const [isHovering, setIsHovering] = useState(false);
const [progress, setProgress] = useState(0);
const activeTab = tabs.find((tab) => tab.id === activeTabId) || tabs[0];
const activeIndex = tabs.findIndex((tab) => tab.id === activeTabId);
// Auto-play logic
useEffect(() => {
if (isHovering) return;
const startTime = Date.now();
const interval = setInterval(() => {
const elapsed = Date.now() - startTime;
const newProgress = (elapsed / autoPlayInterval) * 100;
if (newProgress >= 100) {
const nextIndex = (activeIndex + 1) % tabs.length;
setActiveTabId(tabs[nextIndex].id);
setProgress(0);
} else {
setProgress(newProgress);
}
}, 16); // ~60fps
return () => clearInterval(interval);
}, [activeTabId, isHovering, autoPlayInterval, activeIndex, tabs.length]);
// Mouse move logic for tilt and spotlight
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const mouseXSpring = useSpring(mouseX, { stiffness: 500, damping: 100 });
const mouseYSpring = useSpring(mouseY, { stiffness: 500, damping: 100 });
const rotateX = useTransform(mouseYSpring, [-0.5, 0.5], ["7deg", "-7deg"]);
const rotateY = useTransform(mouseXSpring, [-0.5, 0.5], ["-7deg", "7deg"]);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
// Calculate percentage for rotation
const xPct = (e.clientX - rect.left) / width - 0.5;
const yPct = (e.clientY - rect.top) / height - 0.5;
mouseX.set(xPct);
mouseY.set(yPct);
};
const handleMouseLeave = () => {
mouseX.set(0);
mouseY.set(0);
setIsHovering(false);
};
// Spotlight gradient
const spotlightStyle = useMotionTemplate`radial-gradient(600px circle at ${useTransform(mouseX, [-0.5, 0.5], ["0%", "100%"])} ${useTransform(mouseY, [-0.5, 0.5], ["0%", "100%"])}, rgba(255,255,255,0.1), transparent 40%)`;
return (
<section className="py-24 sm:py-32 overflow-hidden bg-background text-foreground">
<div className="container mx-auto px-4 sm:px-6">
{/* Header */}
<div className="max-w-3xl mx-auto text-center mb-20 space-y-6">
{badge && (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-sm font-medium"
>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
</span>
{badge}
</motion.div>
)}
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-4xl md:text-6xl font-bold tracking-tight"
>
{headline}
</motion.h2>
{description && (
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="text-lg md:text-xl text-muted-foreground leading-relaxed max-w-2xl mx-auto"
>
{description}
</motion.p>
)}
</div>
<div className="grid lg:grid-cols-12 gap-12 lg:gap-20 items-start">
{/* Left Column: Navigation */}
<div className="lg:col-span-5 flex flex-col gap-4">
{tabs.map((tab) => {
const isActive = activeTabId === tab.id;
return (
<div
key={tab.id}
className="relative"
onMouseEnter={() => {
setIsHovering(true);
setActiveTabId(tab.id);
setProgress(0);
}}
onMouseLeave={() => setIsHovering(false)}
>
<button
onClick={() => setActiveTabId(tab.id)}
className={cn(
"w-full text-left p-6 rounded-2xl transition-all duration-500 border border-transparent",
isActive
? "bg-card/50 border-border/50 shadow-lg backdrop-blur-sm"
: "hover:bg-muted/30",
)}
>
<div className="flex items-center gap-4 mb-2">
{tab.icon && (
<div
className={cn(
"p-2 rounded-lg transition-colors duration-300",
isActive
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground",
)}
>
<tab.icon className="w-5 h-5" />
</div>
)}
<span
className={cn(
"text-lg font-bold transition-colors duration-300",
isActive
? "text-foreground"
: "text-muted-foreground",
)}
>
{tab.label}
</span>
</div>
<AnimatePresence>
{isActive && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3 }}
className="overflow-hidden"
>
<p className="text-muted-foreground leading-relaxed mt-2 mb-4">
{tab.description}
</p>
{tab.cta && (
<div className="flex items-center text-primary font-medium text-sm group cursor-pointer">
{tab.cta.text}
<ArrowRight className="w-4 h-4 ml-1 transition-transform group-hover:translate-x-1" />
</div>
)}
</motion.div>
)}
</AnimatePresence>
</button>
{/* Progress Bar for Active Tab */}
{isActive && (
<div className="absolute bottom-0 left-6 right-6 h-0.5 bg-muted overflow-hidden rounded-full">
<motion.div
className="h-full bg-primary"
style={{ width: `${progress}%` }}
transition={{ duration: 0 }}
/>
</div>
)}
</div>
);
})}
</div>
{/* Right Column: Immersive Visual */}
<div className="lg:col-span-7 relative perspective-1000">
<motion.div
style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
className="relative aspect-[4/3] w-full rounded-3xl overflow-hidden border border-white/10 bg-black/50 shadow-2xl backdrop-blur-sm group"
>
{/* Window Controls */}
<div className="absolute top-4 left-4 z-20 flex gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
<div className="w-3 h-3 rounded-full bg-green-500/80" />
</div>
<AnimatePresence mode="wait">
<motion.div
key={activeTab.id}
initial={{ opacity: 0, scale: 1.1, filter: "blur(10px)" }}
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
exit={{ opacity: 0, scale: 0.9, filter: "blur(10px)" }}
transition={{ duration: 0.7, ease: "circOut" }}
className="absolute inset-0"
>
<Image
src={activeTab.image}
alt={activeTab.title}
fill
className="object-cover opacity-90"
priority
/>
{/* Dynamic Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-tr from-black/80 via-black/20 to-transparent" />
</motion.div>
</AnimatePresence>
{/* Floating Glass Card for Content */}
<motion.div
className="absolute bottom-6 left-6 right-6 p-6 rounded-2xl bg-white/10 border border-white/10 backdrop-blur-xl shadow-lg translate-z-20"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<div className="flex items-start justify-between gap-4">
<div>
<h3 className="text-xl font-bold text-white mb-2">
{activeTab.title}
</h3>
<p className="text-white/70 text-sm leading-relaxed">
{activeTab.description}
</p>
</div>
{activeTab.icon && (
<div className="p-3 rounded-xl bg-white/10 text-white hidden sm:block">
<activeTab.icon className="w-6 h-6" />
</div>
)}
</div>
</motion.div>
{/* Spotlight Effect */}
<motion.div
className="absolute inset-0 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-500"
style={{
background: spotlightStyle,
}}
/>
</motion.div>
{/* Background Glows */}
<div className="absolute -inset-10 bg-gradient-to-tr from-primary/20 via-purple-500/20 to-blue-500/20 blur-3xl -z-10 opacity-50 animate-pulse" />
</div>
</div>
</div>
</section>
);
}Update the import paths to match your project setup.
Features
- ✅ Vertical Auto-Play - Automatically cycles through tabs with a progress bar
- ✅ 3D Tilt Effect - Interactive 3D tilt on the feature image container
- ✅ Spotlight Effect - Dynamic mouse-following spotlight on the image container
- ✅ Glassmorphism - Premium frosted glass effects for active states and cards
- ✅ Smooth Transitions - Fluid animations for content switching and height adjustments
- ✅ Responsive Design - Stacks gracefully on mobile, side-by-side on desktop
- ✅ Interactive Hover - Pauses auto-play on hover for better usability
- ✅ Customizable Speed - Adjustable auto-play interval
- ✅ TypeScript - Full type safety
Usage
Basic Usage
import FeatureTabs from "@/components/ui/feature-tabs";
import { Zap, Shield, Rocket } from "lucide-react";
export default function FeaturesPage() {
return (
<FeatureTabs
headline="Powerful features"
tabs={[
{
id: "performance",
label: "Performance",
icon: Zap,
title: "Lightning fast",
description: "Built for speed from the ground up.",
image: "/images/performance.jpg",
},
{
id: "security",
label: "Security",
icon: Shield,
title: "Secure by default",
description: "Enterprise-grade protection for your data.",
image: "/images/security.jpg",
},
]}
/>
);
}Full Example with CTA and Auto-Play
import FeatureTabs from "@/components/ui/feature-tabs";
import { Zap, Shield, Rocket } from "lucide-react";
export default function FeaturesPage() {
return (
<FeatureTabs
badge="Platform Features"
headline="Everything you need"
description="Explore our comprehensive suite of tools."
autoPlayInterval={6000} // 6 seconds per tab
tabs={[
{
id: "performance",
label: "Performance",
icon: Zap,
title: "Lightning-fast performance",
description:
"Built for speed with edge computing and intelligent caching.",
image: "/images/performance.png",
cta: {
text: "View Benchmarks",
onClick: () => console.log("CTA clicked"),
},
},
{
id: "security",
label: "Security",
icon: Shield,
title: "Enterprise-grade security",
description: "Your data is protected with industry-leading security.",
image: "/images/security.png",
cta: {
text: "Security Overview",
href: "/security",
},
},
]}
/>
);
}Props
FeatureTabsProps
| Prop | Type | Default | Description |
|---|---|---|---|
badge | string | undefined | Optional badge text above headline |
headline | string | Required | Main section headline |
description | string | undefined | Optional description below headline |
tabs | Tab[] | Required | Array of tab objects |
autoPlayInterval | number | 5000 | Duration in ms for each tab |
Tab Object
| Prop | Type | Description |
|---|---|---|
id | string | Unique identifier for the tab |
label | string | Tab label text |
icon | LucideIcon | Icon component for the tab |
title | string | Title shown in tab content |
description | string | Description shown in tab content |
image | string | Image URL for the visual side |
cta | object | Optional call-to-action button |
cta.text | string | Button text |
cta.href | string | Optional link URL |
cta.onClick | () => void | Optional click handler |
TypeScript Interface
import { LucideIcon } from "lucide-react";
interface Tab {
id: string;
label: string;
title: string;
description: string;
icon?: LucideIcon;
image: string;
cta?: {
text: string;
href?: string;
onClick?: () => void;
};
}
interface FeatureTabsProps {
badge?: string;
headline: string;
description?: string;
tabs: Tab[];
autoPlayInterval?: number;
}Customization
Adjust Auto-Play Speed
Control how fast the tabs cycle by changing the autoPlayInterval prop (in milliseconds).
<FeatureTabs
autoPlayInterval={3000} // Fast: 3 seconds
// ...
/>Disable Auto-Play
To effectively disable auto-play, set a very large interval.
<FeatureTabs
autoPlayInterval={999999}
// ...
/>Customizing the 3D Tilt
The 3D tilt effect is handled by Framer Motion's useSpring and useTransform. You can adjust the stiffness, damping, and rotation range in the source code:
// In feature-tabs.tsx
const mouseXSpring = useSpring(mouseX, { stiffness: 500, damping: 100 });
const mouseYSpring = useSpring(mouseY, { stiffness: 500, damping: 100 });
// Adjust rotation degrees here
const rotateX = useTransform(mouseYSpring, [-0.5, 0.5], ["7deg", "-7deg"]);
const rotateY = useTransform(mouseXSpring, [-0.5, 0.5], ["-7deg", "7deg"]);Best Practices
- High-Quality Images - The right column is image-heavy. Use high-resolution, visually appealing images.
- Concise Text - Keep descriptions short (2-3 sentences) to prevent layout shifts.
- Meaningful Icons - Use icons that clearly represent the tab category.
- Limit Tabs - 3-5 tabs is the sweet spot for this layout.
- Test Responsiveness - Ensure images look good on both desktop and mobile.