Component

Tree View

Free, copy-pasteable Tailwind CSS Tree View component. Accessible, fully responsive, dark-mode ready, and customizable.

Install via CLI

Run this command to automatically add the component and its dependencies to your project.

npx @abhaysinghr516/business-wish add tree-view
New to the CLI? Run npx @abhaysinghr516/business-wish init first to initialize your project.

Organize deep hierarchical data beautifully with a Tailwind tree view. Whether you are building a full-featured file explorer or navigating complex categories, these Tailwind CSS folder structure components support nested collapsibility, clean indentation, and aesthetics.

A tree view is the perfect companion for a robust Sidebar navigation scheme, serving as a more scalable alternative to a standard Accordion for deeply nested relationships.

Basic Minimal Tree View

Preview
import React, { useState } from "react";
import { ChevronRight, Folder, File, Component, Terminal, Database, Shield, LayoutGrid, ImageIcon, FileImage } from "lucide-react";

export const defaultTreeData = [
  {
    id: "src",
    name: "src",
    type: "folder",
    icon: <Folder className="w-4 h-4 text-neutral-400 dark:text-neutral-500" />,
    children: [
      {
        id: "components",
        name: "components",
        type: "folder",
        icon: <Component className="w-4 h-4 text-neutral-400 dark:text-neutral-500" />,
        children: [
          { id: "Button.tsx", name: "Button.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400 dark:text-neutral-500" /> },
          { id: "Card.tsx", name: "Card.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400 dark:text-neutral-500" /> },
          { id: "Input.tsx", name: "Input.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400 dark:text-neutral-500" /> }
        ]
      },
      { id: "index.tsx", name: "index.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400 dark:text-neutral-500" /> }
    ]
  },
  { id: "package.json", name: "package.json", type: "file", icon: <File className="w-4 h-4 text-neutral-400 dark:text-neutral-500" /> }
];

const BasicTreeNode = ({ node, level }) => {
  const [isOpen, setIsOpen] = useState(false);
  const isFolder = node.type === "folder";

  return (
    <div className="w-full">
      <div 
        className="flex items-center py-1.5 px-2 rounded-xl cursor-pointer transition-colors hover:bg-neutral-200/50 dark:hover:bg-neutral-800/50 text-neutral-800 dark:text-neutral-200"
        style={{ paddingLeft: `${level * 16 + 8}px` }}
        onClick={() => isFolder && setIsOpen(!isOpen)}
      >
        <span className="w-4 h-4 mr-1 flex items-center justify-center">
          {isFolder && (
            <ChevronRight 
              className={`w-3.5 h-3.5 text-neutral-400 dark:text-neutral-500 transition-transform duration-200 ${isOpen ? "rotate-90" : ""}`} 
            />
          )}
        </span>
        {node.icon ? (
          <span className="mr-2.5 shrink-0">{node.icon}</span>
        ) : (
          isFolder ? <Folder className="w-4 h-4 mr-2.5 text-neutral-400 shrink-0" /> : <File className="w-4 h-4 mr-2.5 text-neutral-400 shrink-0" />
        )}
        <span className="text-xs font-mono tracking-tight truncate flex-1">
          {node.name}
        </span>
      </div>
      
      {isFolder && (
        <div 
          className="overflow-hidden transition-all duration-300 ease-in-out"
          style={{ maxHeight: isOpen ? "1000px" : "0", opacity: isOpen ? 1 : 0 }}
        >
          {node.children?.map(child => (
            <BasicTreeNode key={child.id} node={child} level={level + 1} />
          ))}
        </div>
      )}
    </div>
  );
};

export const BasicTreeView = ({ data = defaultTreeData }) => {
  return (
    <div className="bg-white dark:bg-neutral-950 px-4 py-10 flex justify-center items-center">
      <div className="w-full max-w-sm bg-neutral-50 dark:bg-neutral-900/40 border border-neutral-200/80 dark:border-neutral-800/80 rounded-3xl p-5 hover:border-neutral-300 dark:hover:border-neutral-700 transition-all duration-300">
        <div className="flex items-center justify-between px-2 mb-3">
          <h3 className="text-xs font-mono uppercase tracking-wider text-neutral-900 dark:text-white font-medium">Project Explorer</h3>
          <span className="text-[10px] font-mono text-neutral-400 uppercase">TREE // 01</span>
        </div>
        <div className="flex flex-col space-y-0.5">
          {data.map(node => (
            <BasicTreeNode key={node.id} node={node} level={0} />
          ))}
        </div>
      </div>
    </div>
  );
};

MacOS Finder Style Tree View

Preview
import React, { useState } from "react";
import { ChevronRight, Folder, File } from "lucide-react";

const FinderTreeNode = ({ node, level, activeId, onSelect }) => {
  const [isOpen, setIsOpen] = useState(level === 0);
  const isFolder = node.type === "folder";
  const isActive = activeId === node.id;

  return (
    <div className="w-full">
      <div 
        className={`flex items-center py-1.5 px-2 rounded-xl cursor-pointer transition-colors ${
          isActive 
            ? "bg-neutral-900 text-white dark:bg-white dark:text-neutral-950 font-medium" 
            : "hover:bg-neutral-200/50 dark:hover:bg-neutral-800/50 text-neutral-800 dark:text-neutral-200"
        }`}
        style={{ paddingLeft: `${level * 18 + 8}px` }}
        onClick={() => {
          onSelect(node.id);
          if (isFolder) setIsOpen(!isOpen);
        }}
      >
        <span className="w-4 h-4 mr-1 flex items-center justify-center shrink-0">
          {isFolder && (
            <ChevronRight 
              className={`w-3.5 h-3.5 transition-transform duration-200 ${isOpen ? "rotate-90" : ""} ${isActive ? "text-white dark:text-neutral-950" : "text-neutral-400"}`} 
            />
          )}
        </span>
        {isFolder ? (
          <Folder className={`w-4 h-4 mr-2.5 shrink-0 ${isActive ? "text-white dark:text-neutral-950" : "text-neutral-400"}`} />
        ) : (
          <File className={`w-4 h-4 mr-2.5 shrink-0 ${isActive ? "text-white/80 dark:text-neutral-950/80" : "text-neutral-400"}`} />
        )}
        <span className="text-xs font-mono tracking-tight truncate flex-1">
          {node.name}
        </span>
      </div>
      
      {isFolder && isOpen && (
        <div className="flex flex-col">
          {node.children?.map(child => (
            <FinderTreeNode key={child.id} node={child} level={level + 1} activeId={activeId} onSelect={onSelect} />
          ))}
        </div>
      )}
    </div>
  );
};

export const FinderTreeView = ({ data = defaultTreeData }) => {
  const [activeId, setActiveId] = useState("src");

  return (
    <div className="bg-white dark:bg-neutral-950 px-4 py-10 flex justify-center items-center">
      <div className="w-full max-w-sm bg-neutral-50 dark:bg-neutral-900/40 border border-neutral-200/80 dark:border-neutral-800/80 rounded-3xl overflow-hidden hover:border-neutral-300 dark:hover:border-neutral-700 transition-all duration-300">
        <div className="h-10 bg-neutral-100 dark:bg-neutral-900 border-b border-neutral-200/80 dark:border-neutral-800/80 flex items-center justify-between px-4">
          <div className="flex items-center gap-1.5">
            <div className="w-2.5 h-2.5 rounded-full bg-neutral-300 dark:bg-neutral-700" />
            <div className="w-2.5 h-2.5 rounded-full bg-neutral-300 dark:bg-neutral-700" />
            <div className="w-2.5 h-2.5 rounded-full bg-neutral-300 dark:bg-neutral-700" />
          </div>
          <span className="text-[11px] font-mono uppercase tracking-wider text-neutral-400 dark:text-neutral-500">my-app</span>
        </div>
        
        <div className="p-3 max-h-[380px] overflow-y-auto">
          {data.map(node => (
            <FinderTreeNode key={node.id} node={node} level={0} activeId={activeId} onSelect={setActiveId} />
          ))}
        </div>
      </div>
    </div>
  );
};

Hierarchical Line Tree View

Preview
import React, { useState } from "react";

const LineTreeNode = ({ node, level, isLast }) => {
  const [isOpen, setIsOpen] = useState(true);
  const isFolder = node.type === "folder";

  return (
    <div className="w-full relative">
      {level > 0 && (
        <div 
          className="absolute border-l border-neutral-200/80 dark:border-neutral-800/80" 
          style={{ 
            left: `${level * 20 - 10}px`, 
            top: "-10px", 
            bottom: isLast ? "14px" : "-10px" 
          }} 
        />
      )}
      
      {level > 0 && (
        <div 
          className="absolute border-t border-neutral-200/80 dark:border-neutral-800/80 w-2.5" 
          style={{ 
            left: `${level * 20 - 10}px`, 
            top: "14px" 
          }} 
        />
      )}

      <div 
        className="flex items-center py-1.5 px-2 rounded-xl cursor-pointer hover:bg-neutral-200/50 dark:hover:bg-neutral-800/50 transition-colors w-full"
        style={{ paddingLeft: `${level * 20}px` }}
        onClick={() => isFolder && setIsOpen(!isOpen)}
      >
        {isFolder ? (
          <div className="w-4 h-4 flex items-center justify-center mr-2 bg-white dark:bg-neutral-900 rounded-md border border-neutral-200/80 dark:border-neutral-800/80 text-[11px] font-mono text-neutral-600 dark:text-neutral-400 shrink-0">
            {isOpen ? "-" : "+"}
          </div>
        ) : (
          <div className="w-4 h-4 mr-2 shrink-0" />
        )}
        
        <span className="text-xs font-mono text-neutral-800 dark:text-neutral-200 truncate">
          {node.name}
        </span>
      </div>
      
      {isFolder && isOpen && (
        <div className="flex flex-col">
          {node.children?.map((child, index) => (
            <LineTreeNode 
              key={child.id} 
              node={child} 
              level={level + 1} 
              isLast={index === node.children.length - 1} 
            />
          ))}
        </div>
      )}
    </div>
  );
};

export const LineTreeView = ({ data = defaultTreeData }) => {
  return (
    <div className="bg-white dark:bg-neutral-950 px-4 py-10 flex justify-center items-center">
      <div className="w-full max-w-sm bg-neutral-50 dark:bg-neutral-900/40 p-5 rounded-3xl border border-neutral-200/80 dark:border-neutral-800/80 hover:border-neutral-300 dark:hover:border-neutral-700 transition-all duration-300">
        <div className="flex items-center justify-between px-2 mb-3">
          <h3 className="text-xs font-mono uppercase tracking-wider text-neutral-900 dark:text-white font-medium">Hierarchical Tree</h3>
          <span className="text-[10px] font-mono text-neutral-400 uppercase">TREE // 02</span>
        </div>
        <div className="flex flex-col">
          {data.map((node, i) => (
            <LineTreeNode key={node.id} node={node} level={0} isLast={i === data.length - 1} />
          ))}
        </div>
      </div>
    </div>
  );
};

Settings Tree View

Preview
import React, { useState } from "react";
import { ChevronRight, Folder, File } from "lucide-react";

const SettingsTreeNode = ({ node, level }) => {
  const [isOpen, setIsOpen] = useState(level === 0);
  const isFolder = node.type === "folder";

  return (
    <div className="w-full border-b border-neutral-200/60 dark:border-neutral-800/60 last:border-0">
      <div 
        className="flex items-center py-3 px-4 cursor-pointer transition-colors hover:bg-neutral-200/40 dark:hover:bg-neutral-800/40"
        style={{ paddingLeft: `${level * 20 + 16}px` }}
        onClick={() => isFolder && setIsOpen(!isOpen)}
      >
        <span className="mr-3 p-1.5 rounded-xl bg-white dark:bg-neutral-900 text-neutral-500 border border-neutral-200/60 dark:border-neutral-800/60 shrink-0">
          {node.icon ? node.icon : (isFolder ? <Folder className="w-3.5 h-3.5" /> : <File className="w-3.5 h-3.5" />)}
        </span>
        <span className="text-xs font-mono text-neutral-900 dark:text-white truncate flex-1 font-medium">
          {node.name}
        </span>
        {isFolder && (
          <ChevronRight 
            className={`w-3.5 h-3.5 text-neutral-400 transition-transform duration-200 ${isOpen ? "rotate-90" : ""}`} 
          />
        )}
      </div>
      
      {isFolder && (
        <div 
          className="overflow-hidden transition-all duration-300 ease-in-out"
          style={{ maxHeight: isOpen ? "800px" : "0", opacity: isOpen ? 1 : 0 }}
        >
          {node.children?.map(child => (
            <SettingsTreeNode key={child.id} node={child} level={level + 1} />
          ))}
        </div>
      )}
    </div>
  );
};

export const SettingsTreeView = ({ data = defaultTreeData }) => {
  return (
    <div className="bg-white dark:bg-neutral-950 px-4 py-10 flex justify-center items-center">
      <div className="w-full max-w-sm bg-neutral-50 dark:bg-neutral-900/40 border border-neutral-200/80 dark:border-neutral-800/80 rounded-3xl overflow-hidden hover:border-neutral-300 dark:hover:border-neutral-700 transition-all duration-300">
        <div className="flex flex-col">
          {data.map(node => (
            <SettingsTreeNode key={node.id} node={node} level={0} />
          ))}
        </div>
      </div>
    </div>
  );
};