Component
Tree View
Free, copy-pasteable Tailwind CSS Tree View component. Accessible, fully responsive, dark-mode ready, and customizable.
On this page
Basic Minimal Tree ViewMacOS Finder Style Tree ViewHierarchical Line Tree ViewSettings Tree View
AI NewsletterRead →
How Machines Think
Sponsor Business Wish (500+ Devs)Claim this spot →
Install via CLI
Run this command to automatically add the component and its dependencies to your project.
npx @abhaysinghr516/business-wish add tree-viewNew 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
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
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
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
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>
);
};
On this page
AI Newsletter
How Machines Think
Deep dives into logic, mathematics & neural networks.
Read on Substack