- Gallery
- Data Display
- TreeView
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 buttonHow 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"23import * as React from "react"4import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"5import { Button } from "@/components/ui/button"6import { cn } from "@/lib/utils"78interface TreeViewProps {9 className?: string10}1112interface TreeNode {13 id: string14 name: string15 type: "folder" | "file"16 size?: string17 children?: TreeNode[]18}1920const 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]6970interface TreeNodeProps {71 node: TreeNode72 depth: number73 expanded: Set<string>74 onToggle: (id: string) => void75}7677function TreeNode({ node, depth, expanded, onToggle }: TreeNodeProps) {78 const isExpanded = expanded.has(node.id)79 const hasChildren = node.children && node.children.length > 08081 return (82 <div>83 <div84 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 <Button92 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}121122export function TreeView({ className }: TreeViewProps) {123 const [expanded, setExpanded] = React.useState<Set<string>>(new Set(["src", "components"]))124125 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 next134 })135 }136137 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}