Vertical Tabs

Navigation

Vertically oriented tab navigation.

Preview

General settings panel content.

Usage

example.jsx
import { VerticalTabs, VerticalTabsList, VerticalTabsTrigger, VerticalTabsContent } from "@/components/ui/vertical-tabs";

export default function Example() {
  return <VerticalTabs />;
}

Source Code

Copy this file into components/ui/vertical-tabs.jsx in your project.

vertical-tabs.jsx
"use client";

import { forwardRef, createContext, useContext, useState } from "react";
import { cn } from "@/lib/utils";

const VTabsContext = createContext({ value: "", onChange: () => {} });

const VerticalTabs = forwardRef(({ className, value: cv, defaultValue = "", onValueChange, children, ...props }, ref) => {
  const [uv, setUv] = useState(defaultValue);
  const value = cv !== undefined ? cv : uv;
  const onChange = (v) => { setUv(v); onValueChange?.(v); };
  return (
    <VTabsContext.Provider value={{ value, onChange }}>
      <div ref={ref} className={cn("flex gap-4", className)} {...props}>{children}</div>
    </VTabsContext.Provider>
  );
});
VerticalTabs.displayName = "VerticalTabs";

const VerticalTabsList = forwardRef(({ className, ...props }, ref) => (
  <div ref={ref} role="tablist" aria-orientation="vertical" className={cn("flex w-48 flex-col gap-1 border-r pr-4", className)} {...props} />
));
VerticalTabsList.displayName = "VerticalTabsList";

const VerticalTabsTrigger = forwardRef(({ className, value, ...props }, ref) => {
  const ctx = useContext(VTabsContext);
  return (
    <button ref={ref} role="tab" aria-selected={ctx.value === value} onClick={() => ctx.onChange(value)}
      className={cn("w-full rounded-md px-3 py-2 text-left text-sm font-medium transition-colors",
        ctx.value === value ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
        className
      )}
      {...props}
    />
  );
});
VerticalTabsTrigger.displayName = "VerticalTabsTrigger";

const VerticalTabsContent = forwardRef(({ className, value, ...props }, ref) => {
  const ctx = useContext(VTabsContext);
  if (ctx.value !== value) return null;
  return <div ref={ref} role="tabpanel" className={cn("flex-1", className)} {...props} />;
});
VerticalTabsContent.displayName = "VerticalTabsContent";

export { VerticalTabs, VerticalTabsList, VerticalTabsTrigger, VerticalTabsContent };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge