New

TreeView

Expandable file tree with chevron toggles, indent guides, and icons

Data Displaytreefileexpandablehierarchy

Dependencies

shadcn/ui components needed:

npx shadcn@latest add cardnpx shadcn@latest add button

How to use this component

Copy the code below into your project. Make sure you have the required shadcn/ui dependencies installed. Then import and use the component in your pages or layouts.

Code

1"use client"
2
3import * as React from "react"
4import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
5import { Button } from "@/components/ui/button"
6import { cn } from "@/lib/utils"
7
8interface TreeViewProps {
9 className?: string
10}
11
12interface TreeNode {
13 id: string
14 name: string
15 type: "folder" | "file"
16 size?: string
17 children?: TreeNode[]
18}
19
20const treeData: TreeNode[] = [
21 {
22 id: "src",
23 name: "src",
24 type: "folder",
25 children: [
26 {
27 id: "components",
28 name: "components",
29 type: "folder",
30 children: [
31 { id: "button", name: "Button.tsx", type: "file", size: "2.4 KB" },
32 { id: "card", name: "Card.tsx", type: "file", size: "1.8 KB" },
33 { id: "input", name: "Input.tsx", type: "file", size: "3.1 KB" },
34 ],
35 },
36 {
37 id: "lib",
38 name: "lib",
39 type: "folder",
40 children: [
41 { id: "utils", name: "utils.ts", type: "file", size: "4.2 KB" },
42 { id: "api", name: "api.ts", type: "file", size: "2.9 KB" },
43 ],
44 },
45 {
46 id: "app",
47 name: "app",
48 type: "folder",
49 children: [
50 { id: "page", name: "page.tsx", type: "file", size: "5.6 KB" },
51 { id: "layout", name: "layout.tsx", type: "file", size: "1.2 KB" },
52 ],
53 },
54 { id: "index", name: "index.ts", type: "file", size: "0.8 KB" },
55 ],
56 },
57 {
58 id: "public",
59 name: "public",
60 type: "folder",
61 children: [
62 { id: "favicon", name: "favicon.ico", type: "file", size: "15.2 KB" },
63 { id: "robots", name: "robots.txt", type: "file", size: "0.2 KB" },
64 ],
65 },
66 { id: "package", name: "package.json", type: "file", size: "1.5 KB" },
67 { id: "readme", name: "README.md", type: "file", size: "3.8 KB" },
68]
69
70interface TreeNodeProps {
71 node: TreeNode
72 depth: number
73 expanded: Set<string>
74 onToggle: (id: string) => void
75}
76
77function TreeNode({ node, depth, expanded, onToggle }: TreeNodeProps) {
78 const isExpanded = expanded.has(node.id)
79 const hasChildren = node.children && node.children.length > 0
80
81 return (
82 <div>
83 <div
84 className={cn(
85 "flex items-center gap-2 py-1.5 px-2 rounded-md hover:bg-muted/50 transition-colors cursor-pointer group",
86 depth > 0 && "ml-4 border-l border-border/50 pl-4"
87 )}
88 onClick={() => hasChildren && onToggle(node.id)}
89 >
90 {hasChildren ? (
91 <Button
92 variant="ghost"
93 size="sm"
94 className="h-5 w-5 p-0 hover:bg-muted"
95 onClick={(e) => {
96 e.stopPropagation()
97 onToggle(node.id)
98 }}
99 >
100 <span className="text-xs text-muted-foreground">{isExpanded ? "▼" : "▶"}</span>
101 </Button>
102 ) : (
103 <span className="w-5" />
104 )}
105 <span className="text-sm">{node.type === "folder" ? "📁" : "📄"}</span>
106 <span className="text-sm font-medium flex-1">{node.name}</span>
107 {node.size && (
108 <span className="text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">{node.size}</span>
109 )}
110 </div>
111 {isExpanded && hasChildren && (
112 <div>
113 {node.children!.map((child) => (
114 <TreeNode key={child.id} node={child} depth={depth + 1} expanded={expanded} onToggle={onToggle} />
115 ))}
116 </div>
117 )}
118 </div>
119 )
120}
121
122export function TreeView({ className }: TreeViewProps) {
123 const [expanded, setExpanded] = React.useState<Set<string>>(new Set(["src", "components"]))
124
125 const handleToggle = (id: string) => {
126 setExpanded((prev) => {
127 const next = new Set(prev)
128 if (next.has(id)) {
129 next.delete(id)
130 } else {
131 next.add(id)
132 }
133 return next
134 })
135 }
136
137 return (
138 <Card className={cn("w-full max-w-md", className)}>
139 <CardHeader>
140 <CardTitle>Project Files</CardTitle>
141 </CardHeader>
142 <CardContent>
143 <div className="space-y-0.5">
144 {treeData.map((node) => (
145 <TreeNode key={node.id} node={node} depth={0} expanded={expanded} onToggle={handleToggle} />
146 ))}
147 </div>
148 </CardContent>
149 </Card>
150 )
151}

Related Data Display Components

Command Palette

Search for a command to run...