Falnix UI

Tabs System

Five distinct navigation patterns for specifically defined problems. From minimal inline switching to dense configuration sidebars.

Live Preview

Account

Update your profile information. This panel is exclusively for Account configuration. Notice how the layout adapts based on the variant selection.

Recommended Use

  • Use Inline for major page sections (e.g. Profile, Settings).
  • Use Vertical for dense configuration panels with 5+ categories.
  • Use Segmented for high-frequency toggles (e.g. Monthly/Yearly).

Avoid Using When

  • Do not use Inline Tabs for navigation between distinct pages. Use a Sidebar.
  • Do not nest tabs inside tabs unless absolutely necessary (Segmented inside Inline is okay).

Installation

1"use client";
2import React, { useState } from "react";
3import { cn } from "@/lib/utils";
4import { motion } from "framer-motion";
5
6type TabVariant = "inline" | "segmented" | "scrollable" | "vertical" | "pill";
7
8const TabsContext = React.createContext<{
9 activeTab: string;
10 setActiveTab: (id: string) => void;
11 variant: TabVariant;
12} | undefined>(undefined);
13
14export const Tabs = ({
15 defaultValue,
16 variant = "inline",
17 children,
18 className
19}: any) => {
20 const [activeTab, setActiveTab] = useState(defaultValue);
21 return (
22 <TabsContext.Provider value={{ activeTab, setActiveTab, variant }}>
23 <div className={cn("w-full", variant === "vertical" ? "flex gap-8" : "flex flex-col", className)}>
24 {children}
25 </div>
26 </TabsContext.Provider>
27 );
28};
29
30export const TabsList = ({ children, className }: any) => {
31 const { variant } = React.useContext(TabsContext)!;
32 return (
33 <div className={cn(
34 "flex overflow-x-auto no-scrollbar",
35 variant === "inline" && "border-b border-white/5 gap-6",
36 variant === "scrollable" && "border-b border-white/5 gap-6 overflow-x-auto pb-0.5",
37 variant === "segmented" && "bg-white/5 p-1 rounded-lg gap-1 inline-flex self-start",
38 variant === "pill" && "gap-2",
39 variant === "vertical" && "flex-col w-48 shrink-0 border-r border-white/5 gap-1 pr-4",
40 className
41 )}>
42 {children}
43 </div>
44 );
45};
46
47export const TabTrigger = ({ value, children }: any) => {
48 const { activeTab, setActiveTab, variant } = React.useContext(TabsContext)!;
49 const isActive = activeTab === value;
50
51 return (
52 <button
53 onClick={() => setActiveTab(value)}
54 className={cn(
55 "relative text-sm font-medium transition-colors outline-none whitespace-nowrap",
56 (variant === "inline" || variant === "scrollable") && cn(
57 "pb-3 pt-2 text-neutral-400 hover:text-white",
58 isActive ? "text-white" : ""
59 ),
60 variant === "segmented" && cn(
61 "px-3 py-1.5 rounded-md text-neutral-400 z-10",
62 isActive ? "text-white shadow-sm" : "hover:text-white hover:bg-white/5"
63 ),
64 variant === "pill" && cn(
65 "px-4 py-2 rounded-full border border-transparent",
66 isActive ? "bg-white/10 border-white/10 text-white" : "text-neutral-400 hover:text-white hover:bg-white/5"
67 ),
68 variant === "vertical" && cn(
69 "px-4 py-2.5 rounded-md text-left w-full text-neutral-400",
70 isActive ? "bg-white/5 text-white" : "hover:bg-white/5 hover:text-white"
71 )
72 )}
73 >
74 {isActive && (variant === "inline" || variant === "scrollable") && (
75 <motion.div layoutId="active-tab" className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-500" />
76 )}
77 {isActive && variant === "segmented" && (
78 <motion.div layoutId="active-tab" className="absolute inset-0 bg-neutral-700/80 rounded-md -z-10" />
79 )}
80 {children}
81 </button>
82 );
83};
84
85export const TabsContent = ({ value, children }: any) => {
86 const { activeTab } = React.useContext(TabsContext)!;
87 if (activeTab !== value) return null;
88 return <div className="animate-in fade-in slide-in-from-bottom-2 duration-200">{children}</div>;
89};