Fluid Tabs
Tabs that expand when clicked. Revolutionary stuff.
Spring physics so smooth it'll make your other components look choppy. Not my problem.
bunx --bun shadcn@latest add @vcode/fluid-tabsLive Demo
Click them. They expand. Shocking, I know.
Basic
Icons. Labels. Spring physics. Done.
With Notifications
Badges that wiggle on hover. Totally necessary.
Vertical
For sidebars that refuse to be boring.
Custom Colours
Make it match your brand. Or don't. I'm not your boss.
Disabled State
Some tabs just aren't ready for the world yet.
Controlled
Full control. Try not to break anything.
Open on Hover
Hover to expand. Feels expensive.
Size Variants
Small, medium, large. Like ordering coffee but for tabs.
Features
Fluid Motion
Spring physics tuned better than your Spotify algorithm. Labels flow, badges wiggle. Zero jank guaranteed.
Smart Badges
Notification counts that wiggle on hover. Over 99? Becomes "99+" because nobody counts past that anyway.
Flexible Layout
Horizontal or vertical. Dividers or not. Custom colours. Three sizes. Do whatever you want.
Installation
The Easy Way
One command. Everything sorted. Enjoy your free time.
bunx --bun shadcn@latest add @vcode/fluid-tabsThe Hard Way
For people who enjoy suffering. Configure the registry first:
{
"registries": {
"@vcode": "https://ui.vcode.sh/r/{name}.json"
}
}Install dependencies manually:
bun add motion lucide-reactThen add the component:
bunx --bun shadcn@latest add @vcode/fluid-tabsUsage
Basic Usage
Icons, labels, done. Nothing fancy.
import { Home, Bell, Settings } from "lucide-react";
import { FluidTabs } from "@/components/ui/fluid-tabs";
export function Example() {
const items = [
{ label: "Dashboard", icon: Home },
{ label: "Notifications", icon: Bell, notification: 5 },
{ label: "Settings", icon: Settings },
];
return <FluidTabs items={items} />;
}With Notifications
Slap on a notification prop. Numbers or strings, whatever.
const items = [
{ label: "Inbox", icon: Inbox, notification: 12 },
{ label: "Archive", icon: Archive, notification: 3 },
{ label: "Trash", icon: Trash2, notification: "!" },
];
<FluidTabs items={items} />Vertical Direction
Sidebars exist. Set direction="column" and you're sorted.
<FluidTabs
items={items}
direction="column"
/>Custom Colours
Make it yours with highlightColor and className. Or steal someone else's palette.
<FluidTabs
items={items}
highlightColor="text-emerald-500"
className="border-emerald-200 dark:border-emerald-800"
/>With Dividers
Group your tabs. Or don't. The dividers won't judge.
const items = [
{ label: "Home", icon: Home },
{ label: "Settings", icon: Settings },
{ type: "divider" as const },
{ label: "Help", icon: HelpCircle },
{ label: "Logout", icon: LogOut },
];Size Variants
Small, medium, large. Like t-shirts but for UI components. Default is medium because we're not monsters.
<FluidTabs items={items} size="sm" />
<FluidTabs items={items} size="md" />
<FluidTabs items={items} size="lg" />Open on Hover
Expand on hover with configurable delay. Debounced for smooth performance. 75ms default feels right, but you do you.
<FluidTabs
items={items}
openOnHover={true}
hoverDelay={75}
/>API Reference
FluidTabs Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | FluidTabItem[] | - | Array of tabs and dividers |
| highlightColor | string | "text-primary" | Tailwind color class for active tab |
| direction | "row" | "column" | "row" | Layout direction |
| initialTab | number | null | null | Initially selected tab index |
| collapseOnClickAway | boolean | true | Collapse when clicking outside |
| onTabChange | (index: number | null) => void | - | Callback when selection changes |
| size | "sm" | "md" | "lg" | "md" | Size variant for tabs |
| rotateIcon | boolean | false | Enable 360° icon rotation on activation |
| openOnHover | boolean | false | Expand tabs on mouse hover instead of click |
| hoverDelay | number | 75 | Delay in milliseconds before hover triggers expansion |
FluidTab Item
| Property | Type | Required | Description |
|---|---|---|---|
| label | string | Yes | Text shown when expanded |
| icon | LucideIcon | Yes | Lucide icon component |
| notification | number | string | No | Notification badge content |
| isDisabled | boolean | No | Disable tab interaction |
| type | "divider" | For dividers | Creates visual divider |