Animated Tabs
Sliding pill active indicator tab switcher with Framer Motion layoutId and animated panel transitions.
ReactTailwind CSSFramer Motion
Dependenciesframer-motion, lucide-react
npm install framer-motion lucide-reactPreview
Overview Panel
Explore real-time telemetry, core performance metrics, and activity history in a unified canvas.
Source Code
animated-tabs.tsxtsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Code2, Eye, FileText, Cpu, LucideIcon } from "lucide-react";
export interface TabItem {
id: string;
label: string;
icon?: LucideIcon;
badge?: string;
content: string;
}
export function AnimatedTabs({ tabs }: { tabs?: TabItem[] }) {
const defaultTabs: TabItem[] = [
{ id: "overview", label: "Overview", icon: Eye, content: "Unified canvas telemetry." },
{ id: "source", label: "Source", icon: Code2, badge: "TSX", content: "Self-contained component definitions." },
];
const items = tabs || defaultTabs;
const [activeTab, setActiveTab] = useState(items[0].id);
return (
<div className="w-full max-w-lg mx-auto space-y-4">
<div className="flex items-center gap-1 p-1 rounded-xl bg-zinc-100 dark:bg-zinc-900 border border-zinc-200/80 dark:border-zinc-800/80">
{items.map((tab) => {
const Icon = tab.icon;
const isActive = activeTab === tab.id;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`relative flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-lg text-xs font-medium transition-colors cursor-pointer select-none
${isActive ? "text-zinc-900 dark:text-zinc-100" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-300"}`}
>
{isActive && (
<motion.div
layoutId="active-tab-pill"
className="absolute inset-0 bg-white dark:bg-zinc-800 rounded-lg shadow-sm border border-zinc-200/50 dark:border-zinc-700/50"
transition={{ type: "spring", stiffness: 450, damping: 32 }}
/>
)}
<span className="relative z-10 flex items-center gap-1.5">
{Icon && <Icon className="w-3.5 h-3.5 opacity-80" />}
<span>{tab.label}</span>
</span>
</button>
);
})}
</div>
</div>
);
}Usage
usage.tsxtsx
1
2
3
4
5
6
<AnimatedTabs
tabs={[
{ id: "preview", label: "Preview", icon: Eye, content: "Live canvas" },
{ id: "code", label: "Code", icon: Code2, content: "TSX source" },
]}
/>Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| tabs | TabItem[] | Optional | defaultTabs | Array of tab descriptors with id, label, icon, badge, and content. |
Types
types.tstsx
1
2
3
4
5
6
7
export interface TabItem {
id: string;
label: string;
icon?: LucideIcon;
badge?: string;
content: string;
}