Tree View
Component
Beautifully animated Tailwind tree view components for rendering complex hierarchical data and Tailwind CSS folder structure UI.
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 Apple-tier 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
import React, { useState } from "react";
import { ChevronRight, Folder, File, Component, FileImage, LayoutGrid, Terminal, Database, Shield, Image as ImageIcon } from "lucide-react";
const defaultTreeData = [
{
id: "src",
name: "src",
type: "folder",
icon: <Folder className="w-4 h-4 text-blue-500" />,
children: [
{
id: "components",
name: "components",
type: "folder",
icon: <Component className="w-4 h-4 text-indigo-500" />,
children: [
{ id: "Button.tsx", name: "Button.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400" /> },
{ id: "Card.tsx", name: "Card.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400" /> },
{ id: "Input.tsx", name: "Input.tsx", type: "file", icon: <File className="w-4 h-4 text-neutral-400" /> }
]
},
// ... more nodes
]
},
{ id: "package.json", name: "package.json", type: "file", icon: <File className="w-4 h-4 text-neutral-400" /> },
{ id: "README.md", name: "README.md", type: "file", icon: <File className="w-4 h-4 text-neutral-400" /> }
];
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-lg cursor-pointer transition-colors ${
level === 0 ? "hover:bg-neutral-100 dark:hover:bg-neutral-900" : "hover:bg-neutral-50 dark:hover:bg-neutral-900/50"
}`}
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">{node.icon}</span>
) : (
isFolder ? <Folder className="w-4 h-4 mr-2 text-blue-500" /> : <File className="w-4 h-4 mr-2 text-neutral-500" />
)}
<span className="text-[13px] font-medium text-neutral-700 dark:text-neutral-300 truncate">
{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>
);
};
const BasicTreeView = ({ data = defaultTreeData }) => {
return (
<div className="w-full max-w-sm bg-white dark:bg-[#0A0A0A] border border-neutral-200 dark:border-white/10 rounded-2xl p-4 shadow-sm">
<h3 className="text-[14px] font-bold text-neutral-900 dark:text-white px-2 mb-3 tracking-tight">Project Explorer</h3>
<div className="flex flex-col space-y-0.5">
{data.map(node => (
<BasicTreeNode key={node.id} node={node} level={0} />
))}
</div>
</div>
);
};
export default BasicTreeView;
MacOS Finder Style Tree View
import React, { useState } from "react";
import { ChevronRight, File } from "lucide-react";
// tree data omitted for brevity
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 font-sans">
<div
className={`flex items-center py-1 rounded-md cursor-pointer transition-colors ${
isActive
? "bg-blue-500 text-white"
: "hover:bg-neutral-100 dark:hover:bg-neutral-800 text-neutral-800 dark:text-neutral-200"
}`}
style={{ paddingLeft: `${level * 20 + 4}px` }}
onClick={() => {
onSelect(node.id);
if (isFolder) setIsOpen(!isOpen);
}}
onDoubleClick={() => isFolder && setIsOpen(!isOpen)}
>
<span className="w-4 h-4 mr-1 flex items-center justify-center">
{isFolder && (
<ChevronRight
className={`w-3 h-3 transition-transform duration-100 ${isOpen ? "rotate-90" : ""} ${isActive ? "text-white" : "text-neutral-400"}`}
strokeWidth={3}
/>
)}
</span>
{isFolder ? (
<svg className={`w-4 h-4 mr-2 flex-shrink-0 ${isActive ? "text-white" : "text-blue-400"}`} viewBox="0 0 24 24" fill="currentColor">
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
</svg>
) : (
<File className={`w-4 h-4 mr-2 flex-shrink-0 ${isActive ? "text-white/80" : "text-neutral-400"}`} strokeWidth={2} />
)}
<span className="text-[13px] truncate select-none leading-relaxed tracking-tight">
{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>
);
};
const FinderTreeView = ({ data = defaultTreeData }) => {
const [activeId, setActiveId] = useState("src");
return (
<div className="w-full max-w-sm bg-[#fafafa] dark:bg-[#1e1e1e] border border-neutral-300 dark:border-neutral-800 rounded-xl overflow-hidden shadow-lg shadow-neutral-200/50 dark:shadow-none flex flex-col">
<div className="h-10 bg-gradient-to-b from-white to-[#f0f0f0] dark:from-[#323232] dark:to-[#282828] border-b border-neutral-300 dark:border-neutral-800 flex items-center px-4">
<div className="flex gap-2 mr-4">
<div className="w-3 h-3 rounded-full bg-[#ff5f56] border border-[#e0443e]" />
<div className="w-3 h-3 rounded-full bg-[#ffbd2e] border border-[#dea123]" />
<div className="w-3 h-3 rounded-full bg-[#27c93f] border border-[#1aab29]" />
</div>
<span className="text-[12px] font-semibold text-neutral-600 dark:text-neutral-400 mx-auto select-none">my-app</span>
</div>
<div className="p-2 overflow-y-auto max-h-[400px]">
{data.map(node => (
<FinderTreeNode key={node.id} node={node} level={0} activeId={activeId} onSelect={setActiveId} />
))}
</div>
</div>
);
};
export default FinderTreeView;
Connected Line Tree View
import React, { useState } from "react";
// tree data omitted for brevity
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 dark:border-neutral-800"
style={{ left: `${level * 24 - 12}px`, top: "-12px", bottom: isLast ? "16px" : "-12px" }}
/>
)}
{level > 0 && (
<div
className="absolute border-t border-neutral-200 dark:border-neutral-800 w-3"
style={{ left: `${level * 24 - 12}px`, top: "16px" }}
/>
)}
<div
className="flex items-center py-1.5 rounded-md cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-900/50 transition-colors w-fit pr-4"
style={{ paddingLeft: `${level * 24}px` }}
onClick={() => isFolder && setIsOpen(!isOpen)}
>
{isFolder ? (
<div className="w-5 h-5 flex items-center justify-center mr-2 bg-neutral-100 dark:bg-neutral-800 rounded border border-neutral-200 dark:border-neutral-700 z-10 transition-colors hover:border-neutral-300 dark:hover:border-neutral-600">
<span className="text-[14px] font-mono text-neutral-500 leading-none pb-0.5">{isOpen ? "-" : "+"}</span>
</div>
) : (
<div className="w-5 h-5 mr-2 flex-shrink-0 z-10" />
)}
<span className="text-[14px] text-neutral-700 dark:text-neutral-300 truncate">
{node.name}
</span>
</div>
{isFolder && isOpen && (
<div className="flex flex-col pb-1">
{node.children?.map((child, index) => (
<LineTreeNode key={child.id} node={child} level={level + 1} isLast={index === node.children.length - 1} />
))}
</div>
)}
</div>
);
};
const LineTreeView = ({ data = defaultTreeData }) => {
return (
<div className="w-full max-w-sm bg-white dark:bg-[#050505] p-6 rounded-2xl border border-neutral-200 dark:border-neutral-800 shadow-sm overflow-hidden">
<div className="flex flex-col">
{data.map((node, i) => (
<LineTreeNode key={node.id} node={node} level={0} isLast={i === data.length - 1} />
))}
</div>
</div>
);
};
export default LineTreeView;
Settings Structure Tree View
import React, { useState } from "react";
import { ChevronRight, Folder, File } from "lucide-react";
// tree data omitted for brevity
const SettingsTreeNode = ({ node, level }) => {
const [isOpen, setIsOpen] = useState(level === 0);
const isFolder = node.type === "folder";
return (
<div className="w-full border-b border-neutral-100 dark:border-neutral-800/50 last:border-0">
<div
className="flex items-center py-3 pr-4 group cursor-pointer transition-colors hover:bg-neutral-50/50 dark:hover:bg-neutral-900/30"
style={{ paddingLeft: `${level * 24 + 16}px` }}
onClick={() => isFolder && setIsOpen(!isOpen)}
>
<span className="mr-3 p-1.5 rounded-lg bg-neutral-100 dark:bg-neutral-900 text-neutral-500 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-white transition-colors">
{node.icon ? node.icon : (isFolder ? <Folder className="w-4 h-4" /> : <File className="w-4 h-4" />)}
</span>
<span className="text-[14px] font-medium text-neutral-700 dark:text-neutral-200 truncate flex-1">
{node.name}
</span>
{isFolder && (
<ChevronRight
className={`w-4 h-4 text-neutral-400 transition-transform duration-300 ${isOpen ? "rotate-90" : ""}`}
/>
)}
</div>
{isFolder && (
<div
className="overflow-hidden transition-all duration-300 ease-in-out bg-neutral-50/30 dark:bg-neutral-900/10"
style={{ maxHeight: isOpen ? "800px" : "0", opacity: isOpen ? 1 : 0 }}
>
{node.children?.map(child => (
<SettingsTreeNode key={child.id} node={child} level={level + 1} />
))}
</div>
)}
</div>
);
};
const SettingsTreeView = ({ data = defaultTreeData }) => {
return (
<div className="w-full max-w-sm bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-2xl shadow-xl shadow-neutral-200/20 dark:shadow-none overflow-hidden">
<div className="flex flex-col">
{data.map(node => (
<SettingsTreeNode key={node.id} node={node} level={0} />
))}
</div>
</div>
);
};
export default SettingsTreeView;