Components
Vercel Tabs
A tab component with animated active and hover indicators, inspired by Vercel's dashboard UI.
Last updated on
import { VercelTabs } from "@/components/ui/vercel-tabs";
const tabsData = [
{
label: "Overview",
value: "Overview",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Overview Content</h2>
<p className="text-muted-foreground">
This is the content area for the Overview tab. You can pass any React
components or content here.
</p>
</div>
),
},
{
label: "Integrations",
value: "Integrations",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Integrations</h2>
<p className="text-muted-foreground">
Connect your favorite tools and services.
</p>
</div>
),
},
{
label: "Activity",
value: "Activity",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Activity Log</h2>
<p className="text-muted-foreground">
View the latest activity on your account.
</p>
</div>
),
},
{
label: "Domains",
value: "Domains",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Domains</h2>
<p className="text-muted-foreground">
Manage your custom domains and DNS settings.
</p>
</div>
),
},
{
label: "Usage",
value: "Usage",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Usage Statistics</h2>
<p className="text-muted-foreground">
Check your resource usage and limits.
</p>
</div>
),
},
{
label: "Monitoring",
value: "Monitoring",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Monitoring</h2>
<p className="text-muted-foreground">
Real-time performance monitoring.
</p>
</div>
),
},
];
export function VercelTabsDemo() {
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-3">
<h3 className="font-medium text-sm">Vercel Tabs</h3>
<VercelTabs tabs={tabsData} defaultTab="Overview" />
</div>
</div>
);
}Installation
CLI
npx shadcn@latest add "https://jolyui.dev/r/vercel-tabs"Manual
Copy and paste the following code into your project component/ui/vercel-tabs.tsx
"use client";
import type React from "react";
import { useEffect, useRef, useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
interface TabData {
label: string;
value: string;
content: React.ReactNode;
}
interface VercelTabsProps {
tabs: TabData[];
defaultTab?: string;
className?: string;
}
export function VercelTabs({ tabs, defaultTab, className }: VercelTabsProps) {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.value);
const [hoverStyle, setHoverStyle] = useState({});
const [activeStyle, setActiveStyle] = useState({ left: "0px", width: "0px" });
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
const activeIndex = tabs.findIndex((tab) => tab.value === activeTab);
useEffect(() => {
if (hoveredIndex !== null) {
const hoveredElement = tabRefs.current[hoveredIndex];
if (hoveredElement) {
const { offsetLeft, offsetWidth } = hoveredElement;
setHoverStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
});
}
}
}, [hoveredIndex]);
useEffect(() => {
const activeElement = tabRefs.current[activeIndex];
if (activeElement) {
const { offsetLeft, offsetWidth } = activeElement;
setActiveStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
});
}
}, [activeIndex]);
useEffect(() => {
requestAnimationFrame(() => {
const activeElement = tabRefs.current[activeIndex];
if (activeElement) {
const { offsetLeft, offsetWidth } = activeElement;
setActiveStyle({
left: `${offsetLeft}px`,
width: `${offsetWidth}px`,
});
}
});
}, [activeIndex]);
return (
<Tabs
defaultValue={activeTab}
onValueChange={setActiveTab}
className={`flex w-full flex-col items-center ${className}`}
>
<TabsList className="relative h-auto select-none gap-[6px] bg-transparent p-0">
{/* Hover Highlight */}
<div
className="absolute top-0 left-0 flex h-[30px] items-center rounded-[6px] bg-[#0e0f1114] transition-all duration-300 ease-out dark:bg-[#ffffff1a]"
style={{
...hoverStyle,
opacity: hoveredIndex !== null ? 1 : 0,
}}
/>
{/* Active Indicator */}
<div
className="absolute bottom-[-6px] h-[2px] bg-[#0e0f11] transition-all duration-300 ease-out dark:bg-white"
style={activeStyle}
/>
{tabs.map((tab, index) => (
<TabsTrigger
key={tab.value}
value={tab.value}
ref={(el) => {
tabRefs.current[index] = el;
}}
className={`z-10 h-[30px] cursor-pointer rounded-md border-0 bg-transparent px-3 py-2 outline-none transition-colors duration-300 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 data-[state=active]:bg-transparent data-[state=active]:shadow-none ${
activeTab === tab.value
? "text-[#0e0e10] dark:text-white"
: "text-[#0e0f1199] dark:text-[#ffffff99]"
}`}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
>
<span className="whitespace-nowrap font-medium text-sm leading-5">
{tab.label}
</span>
</TabsTrigger>
))}
</TabsList>
{/* Content Area */}
<div className="mt-8 w-full px-4">
{tabs.map((tab) => (
<TabsContent
key={tab.value}
value={tab.value}
className="fade-in-50 w-full animate-in duration-500"
>
{tab.content}
</TabsContent>
))}
</div>
</Tabs>
);
}Usage
import { VercelTabs } from "@/components/ui/vercel-tabs";
const tabsData = [
{ label: "Overview", value: "Overview", content: <div>Overview Content</div> },
{ label: "Integrations", value: "Integrations", content: <div>Integrations Content</div> },
];
<VercelTabs tabs={tabsData} defaultTab="Overview" />Examples
Basic
import { VercelTabs } from "@/components/ui/vercel-tabs";
const tabsData = [
{
label: "Overview",
value: "Overview",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Overview Content</h2>
<p className="text-muted-foreground">
This is the content area for the Overview tab. You can pass any React
components or content here.
</p>
</div>
),
},
{
label: "Integrations",
value: "Integrations",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Integrations</h2>
<p className="text-muted-foreground">
Connect your favorite tools and services.
</p>
</div>
),
},
{
label: "Activity",
value: "Activity",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Activity Log</h2>
<p className="text-muted-foreground">
View the latest activity on your account.
</p>
</div>
),
},
{
label: "Domains",
value: "Domains",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Domains</h2>
<p className="text-muted-foreground">
Manage your custom domains and DNS settings.
</p>
</div>
),
},
{
label: "Usage",
value: "Usage",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Usage Statistics</h2>
<p className="text-muted-foreground">
Check your resource usage and limits.
</p>
</div>
),
},
{
label: "Monitoring",
value: "Monitoring",
content: (
<div className="rounded-lg border border-gray-200 bg-white p-6 text-black dark:border-[#333] dark:bg-[#1c1c1c] dark:text-white">
<h2 className="mb-2 font-bold text-2xl">Monitoring</h2>
<p className="text-muted-foreground">
Real-time performance monitoring.
</p>
</div>
),
},
];
export function VercelTabsDemo() {
return (
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-3">
<h3 className="font-medium text-sm">Vercel Tabs</h3>
<VercelTabs tabs={tabsData} defaultTab="Overview" />
</div>
</div>
);
}API Reference
VercelTabs Props
Prop
Type
TabData Type
Prop
Type
Notes
- Animated hover and active indicators
- Fully customizable tab data and content
- Uses Tailwind CSS for styling
How is this guide?