Custom Shadcn Tree for React and Tailwind CSS. A customizable tree component for React.
Browse 7 production-ready Shadcn Tree components for dashboards, forms, and product UI. These examples follow the Radix UI implementation with accessible primitives from the Radix stack and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 7 Shadcn Tree components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
import { Tree, TreeItem, TreeItemLabel } from "@/components/reui/r-tree"<Tree tree={tree}>
{tree.getItems().map((item) => (
<TreeItem key={item.getId()} item={item}>
<TreeItemLabel />
</TreeItem>
))}
</Tree>The root component that provides context for the tree structure.
A component representing a single item (node) in the tree.
The component that displays the label and toggle icon for a tree item.
The visual indicator shown during drag-and-drop operations.
"use client"
import {
Tree,
TreeItem,
TreeItemLabel,
} from "@/components/reui/tree"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { useTree } from "@headless-tree/react"
interface Item {
name: string
children?: string[]
}
const items: Record<string, Item> = {
crm: {
name: "CRM",
children: ["leads", "accounts", "activities", "support"],
},
leads: {
name: "Leads",
children: ["new-lead", "contacted-lead", "qualified-lead"],
},
"new-lead": { name: "New Lead" },
"contacted-lead": { name: "Contacted Lead" },
"qualified-lead": { name: "Qualified Lead" },
accounts: {
name: "Accounts",
children: ["acme-corp", "globex-inc"],
},
"acme-corp": {
name: "Acme Corp",
children: ["acme-contacts", "acme-opportunities"],
},
"acme-contacts": {
name: "Contacts",
children: ["john-smith", "jane-doe"],
},
"john-smith": { name: "John Smith" },
"jane-doe": { name: "Jane Doe" },
"acme-opportunities": {
name: "Opportunities",
children: ["website-redesign", "annual-maintenance"],
},
"website-redesign": { name: "Website Redesign" },
"annual-maintenance": { name: "Annual Maintenance" },
"globex-inc": {
name: "Globex Inc",
children: ["globex-contacts", "globex-opportunities"],
},
"globex-contacts": {
name: "Contacts",
children: ["alice-johnson"],
},
"alice-johnson": { name: "Alice Johnson" },
"globex-opportunities": {
name: "Opportunities",
children: ["cloud-migration"],
},
"cloud-migration": { name: "Cloud Migration" },
activities: {
name: "Activities",
children: ["calls", "meetings", "emails"],
},
calls: { name: "Calls" },
meetings: { name: "Meetings" },
emails: { name: "Emails" },
support: {
name: "Support",
children: ["open-tickets", "closed-tickets"],
},
"open-tickets": { name: "Open Tickets" },
"closed-tickets": { name: "Closed Tickets" },
}
const indent = 20
export function Pattern() {
const tree = useTree<Item>({
initialState: {
expandedItems: ["leads", "accounts", "activities"],
},
indent,
rootItemId: "crm",
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) => {
return (
<TreeItem key={item.getId()} item={item}>
<TreeItemLabel />
</TreeItem>
)
})}
</Tree>
</div>
)
}
"use client"
import {
Tree,
TreeItem,
TreeItemLabel,
} from "@/components/reui/tree"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { useTree } from "@headless-tree/react"
interface Item {
name: string
children?: string[]
}
const items: Record<string, Item> = {
crm: {
name: "CRM",
children: ["leads", "accounts", "activities", "support"],
},
leads: {
name: "Leads",
children: ["new-lead", "contacted-lead", "qualified-lead"],
},
"new-lead": { name: "New Lead" },
"contacted-lead": { name: "Contacted Lead" },
"qualified-lead": { name: "Qualified Lead" },
accounts: {
name: "Accounts",
children: ["acme-corp", "globex-inc"],
},
"acme-corp": {
name: "Acme Corp",
children: ["acme-contacts", "acme-opportunities"],
},
"acme-contacts": {
name: "Contacts",
children: ["john-smith", "jane-doe"],
},
"john-smith": { name: "John Smith" },
"jane-doe": { name: "Jane Doe" },
"acme-opportunities": {
name: "Opportunities",
children: ["website-redesign", "annual-maintenance"],
},
"website-redesign": { name: "Website Redesign" },
"annual-maintenance": { name: "Annual Maintenance" },
"globex-inc": {
name: "Globex Inc",
children: ["globex-contacts", "globex-opportunities"],
},
"globex-contacts": {
name: "Contacts",
children: ["alice-johnson"],
},
"alice-johnson": { name: "Alice Johnson" },
"globex-opportunities": {
name: "Opportunities",
children: ["cloud-migration"],
},
"cloud-migration": { name: "Cloud Migration" },
activities: {
name: "Activities",
children: ["calls", "meetings", "emails"],
},
calls: { name: "Calls" },
meetings: { name: "Meetings" },
emails: { name: "Emails" },
support: {
name: "Support",
children: ["open-tickets", "closed-tickets"],
},
"open-tickets": { name: "Open Tickets" },
"closed-tickets": { name: "Closed Tickets" },
}
const indent = 20
export function Pattern() {
const tree = useTree<Item>({
initialState: {
expandedItems: ["leads", "accounts", "activities"],
},
indent,
rootItemId: "crm",
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
className="relative before:absolute before:inset-0 before:-ms-1 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]"
indent={indent}
tree={tree}
>
{tree.getItems().map((item) => {
return (
<TreeItem key={item.getId()} item={item}>
<TreeItemLabel />
</TreeItem>
)
})}
</Tree>
</div>
)
}
"use client"
import {
Tree,
TreeItem,
TreeItemLabel,
} from "@/components/reui/tree"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { useTree } from "@headless-tree/react"
import { FileIcon, FolderIcon, FolderOpenIcon } from 'lucide-react'
interface Item {
name: string
children?: string[]
}
const items: Record<string, Item> = {
crm: {
name: "CRM",
children: ["leads", "accounts", "activities", "support"],
},
leads: {
name: "Leads",
children: ["new-lead", "contacted-lead", "qualified-lead"],
},
"new-lead": { name: "New Lead" },
"contacted-lead": { name: "Contacted Lead" },
"qualified-lead": { name: "Qualified Lead" },
accounts: {
name: "Accounts",
children: ["acme-corp", "globex-inc"],
},
"acme-corp": {
name: "Acme Corp",
children: ["acme-contacts", "acme-opportunities"],
},
"acme-contacts": {
name: "Contacts",
children: ["john-smith", "jane-doe"],
},
"john-smith": { name: "John Smith" },
"jane-doe": { name: "Jane Doe" },
"acme-opportunities": {
name: "Opportunities",
children: ["website-redesign", "annual-maintenance"],
},
"website-redesign": { name: "Website Redesign" },
"annual-maintenance": { name: "Annual Maintenance" },
"globex-inc": {
name: "Globex Inc",
children: ["globex-contacts", "globex-opportunities"],
},
"globex-contacts": {
name: "Contacts",
children: ["alice-johnson"],
},
"alice-johnson": { name: "Alice Johnson" },
"globex-opportunities": {
name: "Opportunities",
children: ["cloud-migration"],
},
"cloud-migration": { name: "Cloud Migration" },
activities: {
name: "Activities",
children: ["calls", "meetings", "emails"],
},
calls: { name: "Calls" },
meetings: { name: "Meetings" },
emails: { name: "Emails" },
support: {
name: "Support",
children: ["open-tickets", "closed-tickets"],
},
"open-tickets": { name: "Open Tickets" },
"closed-tickets": { name: "Closed Tickets" },
}
const indent = 20
export function Pattern() {
const tree = useTree<Item>({
initialState: {
expandedItems: ["leads", "accounts", "activities"],
},
indent,
rootItemId: "crm",
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
className="relative before:absolute before:inset-0 before:-ms-1 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]"
indent={indent}
tree={tree}
>
{tree.getItems().map((item) => {
return (
<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">
{item.isFolder() ? (
item.isExpanded() ? (
<FolderOpenIcon className="text-muted-foreground pointer-events-none size-4" />
) : (
<FolderIcon className="text-muted-foreground pointer-events-none size-4" />
)
) : (
<FileIcon className="text-muted-foreground pointer-events-none size-4" />
)}
{item.getItemName()}
</span>
</TreeItemLabel>
</TreeItem>
)
})}
</Tree>
</div>
)
}
"use client"
import {
Tree,
TreeItem,
TreeItemLabel,
} from "@/components/reui/tree"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { useTree } from "@headless-tree/react"
import { FileIcon, FolderIcon, FolderOpenIcon } from 'lucide-react'
interface Item {
name: string
children?: string[]
}
const items: Record<string, Item> = {
crm: {
name: "CRM",
children: ["leads", "accounts", "activities", "support"],
},
leads: {
name: "Leads",
children: ["new-lead", "contacted-lead", "qualified-lead"],
},
"new-lead": { name: "New Lead" },
"contacted-lead": { name: "Contacted Lead" },
"qualified-lead": { name: "Qualified Lead" },
accounts: {
name: "Accounts",
children: ["acme-corp", "globex-inc"],
},
"acme-corp": {
name: "Acme Corp",
children: ["acme-contacts", "acme-opportunities"],
},
"acme-contacts": {
name: "Contacts",
children: ["john-smith", "jane-doe"],
},
"john-smith": { name: "John Smith" },
"jane-doe": { name: "Jane Doe" },
"acme-opportunities": {
name: "Opportunities",
children: ["website-redesign", "annual-maintenance"],
},
"website-redesign": { name: "Website Redesign" },
"annual-maintenance": { name: "Annual Maintenance" },
"globex-inc": {
name: "Globex Inc",
children: ["globex-contacts", "globex-opportunities"],
},
"globex-contacts": {
name: "Contacts",
children: ["alice-johnson"],
},
"alice-johnson": { name: "Alice Johnson" },
"globex-opportunities": {
name: "Opportunities",
children: ["cloud-migration"],
},
"cloud-migration": { name: "Cloud Migration" },
activities: {
name: "Activities",
children: ["calls", "meetings", "emails"],
},
calls: { name: "Calls" },
meetings: { name: "Meetings" },
emails: { name: "Emails" },
support: {
name: "Support",
children: ["open-tickets", "closed-tickets"],
},
"open-tickets": { name: "Open Tickets" },
"closed-tickets": { name: "Closed Tickets" },
}
const indent = 20
export function Pattern() {
const tree = useTree<Item>({
initialState: {
expandedItems: ["leads", "accounts", "activities"],
},
indent,
rootItemId: "crm",
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
className="relative before:absolute before:inset-0 before:-ms-1.25 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]"
indent={indent}
tree={tree}
toggleIconType="plus-minus"
>
{tree.getItems().map((item) => {
return (
<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="ms-1 flex items-center gap-2">
{item.isFolder() ? (
item.isExpanded() ? (
<FolderOpenIcon className="text-muted-foreground pointer-events-none size-4" />
) : (
<FolderIcon className="text-muted-foreground pointer-events-none size-4" />
)
) : (
<FileIcon className="text-muted-foreground pointer-events-none size-4" />
)}
{item.getItemName()}
</span>
</TreeItemLabel>
</TreeItem>
)
})}
</Tree>
</div>
)
}