# File explorer tree with type icons

> File explorer tree with type icons

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-tree-5).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-tree-5
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-tree-5-light-0da256692a1ce612.png
- Registry item: https://reui.io/r/new-york-v4/c-tree-5.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-tree-5.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@headless-tree/core`, `@headless-tree/react`
- Registry items: `@reui/tree`

## Files

### `c-tree-5.tsx`

```tsx
"use client"

import {
  Tree,
  TreeItem,
  TreeItemLabel,
} from "@/components/reui/tree"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { useTree } from "@headless-tree/react"

import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"

interface FileItem {
  name: string
  children?: string[]
  type?: "folder" | "ts" | "tsx" | "css" | "json" | "md" | "config"
}

const items: Record<string, FileItem> = {
  root: {
    name: "my-project",
    children: ["src", "public", "package-json", "readme", "tsconfig"],
  },
  src: {
    name: "src",
    children: ["app", "components", "lib", "globals-css"],
    type: "folder",
  },
  app: {
    name: "app",
    children: ["page-tsx", "layout-tsx", "loading-tsx"],
    type: "folder",
  },
  "page-tsx": { name: "page.tsx", type: "tsx" },
  "layout-tsx": { name: "layout.tsx", type: "tsx" },
  "loading-tsx": { name: "loading.tsx", type: "tsx" },
  components: {
    name: "components",
    children: ["button-tsx", "card-tsx", "dialog-tsx"],
    type: "folder",
  },
  "button-tsx": { name: "button.tsx", type: "tsx" },
  "card-tsx": { name: "card.tsx", type: "tsx" },
  "dialog-tsx": { name: "dialog.tsx", type: "tsx" },
  lib: { name: "lib", children: ["utils-ts", "api-ts"], type: "folder" },
  "utils-ts": { name: "utils.ts", type: "ts" },
  "api-ts": { name: "api.ts", type: "ts" },
  "globals-css": { name: "globals.css", type: "css" },
  public: { name: "public", children: ["favicon"], type: "folder" },
  favicon: { name: "favicon.ico", type: "config" },
  "package-json": { name: "package.json", type: "json" },
  readme: { name: "README.md", type: "md" },
  tsconfig: { name: "tsconfig.json", type: "json" },
}

const getFileIcon = (type?: string, isExpanded?: boolean) => {
  if (!type || type === "folder") {
    return isExpanded ? (
      <IconPlaceholder
        lucide="FolderOpenIcon"
        tabler="IconFolderOpen"
        hugeicons="FolderOpenIcon"
        phosphor="FolderOpenIcon"
        remixicon="RiFolderOpenLine"
        className="pointer-events-none size-4 text-amber-500"
      />
    ) : (
      <IconPlaceholder
        lucide="FolderIcon"
        tabler="IconFolder"
        hugeicons="FolderIcon"
        phosphor="FolderIcon"
        remixicon="RiFolderLine"
        className="pointer-events-none size-4 text-amber-500"
      />
    )
  }
  if (type === "tsx" || type === "ts") {
    return (
      <IconPlaceholder
        lucide="FileCodeIcon"
        tabler="IconFileCode"
        hugeicons="FileScriptIcon"
        phosphor="FileCodeIcon"
        remixicon="RiFileCodeLine"
        className="pointer-events-none size-4 text-blue-500"
      />
    )
  }
  if (type === "css") {
    return (
      <IconPlaceholder
        lucide="PaletteIcon"
        tabler="IconPalette"
        hugeicons="PaintBoardIcon"
        phosphor="PaletteIcon"
        remixicon="RiPaletteLine"
        className="pointer-events-none size-4 text-purple-500"
      />
    )
  }
  if (type === "json") {
    return (
      <IconPlaceholder
        lucide="BracesIcon"
        tabler="IconBraces"
        hugeicons="CodeIcon"
        phosphor="BracketsCurly"
        remixicon="RiBracesLine"
        className="pointer-events-none size-4 text-yellow-500"
      />
    )
  }
  if (type === "md") {
    return (
      <IconPlaceholder
        lucide="FileTextIcon"
        tabler="IconFileText"
        hugeicons="File02Icon"
        phosphor="FileTextIcon"
        remixicon="RiFileTextLine"
        className="text-muted-foreground pointer-events-none size-4"
      />
    )
  }
  return (
    <IconPlaceholder
      lucide="FileIcon"
      tabler="IconFile"
      hugeicons="FileEmpty02Icon"
      phosphor="FileIcon"
      remixicon="RiFileLine"
      className="text-muted-foreground pointer-events-none size-4"
    />
  )
}

const indent = 20

export function Pattern() {
  const tree = useTree<FileItem>({
    initialState: {
      expandedItems: ["src", "app", "components"],
    },
    indent,
    rootItemId: "root",
    getItemName: (item) => item.getItemData().name,
    isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,
    dataLoader: {
      getItem: (itemId) => items[itemId],
      getChildren: (itemId) => items[itemId].children ?? [],
    },
    features: [syncDataLoaderFeature, hotkeysCoreFeature],
  })

  return (
    <div className="mx-auto w-full grow place-self-start lg:w-xs">
      <Tree indent={indent} tree={tree}>
        {tree.getItems().map((item) => (
          <TreeItem key={item.getId()} item={item}>
            <TreeItemLabel className="before:bg-background relative before:absolute before:inset-x-0 before:-inset-y-0.5 before:-z-10">
              <span className="flex items-center gap-2">
                {getFileIcon(item.getItemData().type, item.isExpanded())}
                {item.getItemName()}
              </span>
            </TreeItemLabel>
          </TreeItem>
        ))}
      </Tree>
    </div>
  )
}
```
