Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Data Display

Tree View

Data Display

Tree View React component for Tailwind CSS v4

TimelineAudit Log
Live props
Behavior
selectionMode
checkboxes
cascade
expandOnClick
draggable
State
defaultExpandAll
Content
searchable
filter
Appearance
size
guides
tone
color
folderIcons
Layout
indent16px
virtualize
maxHeightAuto
Code
import { TreeView } from "@/components/ui/tree-view"

<TreeView />
  1. Home
  2. Components
  3. Data Display
  4. Tree View
Pro

Tree View React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React tree view for folders, categories or permissions: animated expand, selection, tri-state checkboxes, filter and drag to move.

Click a row to select it; clicking a folder also opens or closes it while its children slide in and the chevron turns on a quick spring, and the chevron alone toggles too. Double click a file (or tap it on a phone) to open it. In multiple mode clicks toggle rows and Shift + click selects a range. With checkboxes, clicking a box checks it and, with cascade, everything under it; parents turn to a dash when only some children are checked. With selectionMode none a click on a file row checks it too, while a click on a folder row only opens it. Keyboard (one Tab stop): ArrowDown and ArrowUp move between visible rows, ArrowRight opens a closed folder or steps into an open one, ArrowLeft closes an open folder or jumps to the parent, Home and End go to the first and last row, PageUp and PageDown jump ten rows, Enter opens a file or toggles a folder, Space selects (or checks with checkboxes), Shift + arrows extend a multiple selection, Ctrl+A (Cmd+A on a Mac) selects every visible row, * opens every folder beside the focused one, typing letters jumps to the next row that starts with them (diacritics ignored), Escape clears a multiple selection, and Shift+F10 or the ContextMenu key opens the row's ⋯ menu. With the filter field, typing filters as you go: matches keep their parents, the path to them opens and matched letters turn bold; ArrowDown moves into the tree and Escape clears the field. Folders with lazy children show a spinner row while they load and a Retry row when loading fails. With draggable on, drag a row with the mouse (or long press it on touch) and a line shows where it lands: before or after a row, or a ring around a folder it goes into; hovering a closed folder opens it, and dropping a folder into itself is refused. Alt + ArrowUp and Alt + ArrowDown move the focused row among its siblings, Alt + ArrowRight moves it into the folder above and Alt + ArrowLeft moves it out one level; every move is announced. On a phone rows grow to 44px, the ⋯ button is always visible and opens a bottom sheet, the whole row is the tap target and the filter field uses 16px text. Huge trees only render the rows in view.

Tree View is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add tree-view, together with Button, Checkbox, Dropdown Menu, Empty State, Input and 8 more and the npm packages @dnd-kit/core, @tanstack/react-virtual, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add tree-view

Tree View is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import TreeView from @/components/ui/tree-view.

import { useState } from "react"
import { TreeView, moveTreeNode, type TreeNode } from "@/components/ui/tree-view"

const documents: TreeNode[] = [
  {
    id: "clients",
    label: "Clients",
    children: [
      {
        id: "tech",
        label: "Tech Solutions Inc.",
        children: [
          { id: "contract", label: "Contract 2026.pdf" },
          { id: "invoice", label: "Invoice 2026-0412.pdf" },
        ],
      },
      { id: "logistic", label: "Summit Logistics", hasChildren: true },
    ],
  },
  { id: "reports", label: "Reports", children: [{ id: "q3", label: "Q3 2026.xlsx" }] },
]

export function Documents({ onOpen }: { onOpen: (id: string) => void }) {
  const [items, setItems] = useState(documents)
  return (
    <TreeView
      items={items}
      folderIcons
      searchable
      draggable
      defaultExpandedKeys={["clients", "tech"]}
      loadChildren={async () => [{ id: "framework", label: "Framework contract 2025.pdf" }]}
      onAction={(node) => onOpen(node.id)}
      onMove={(event) => setItems((prev) => moveTreeNode(prev, event))}
      labels={{ tree: "Documents", search: "Search documents" }}
    />
  )
}

Props

  • items*
    TreeNode[]

    The tree: { id, label, textValue?, icon?, expandedIcon?, children?, hasChildren?, disabled?, badge?, description?, href?, actions?: MenuEntry[], draggable?, droppable? }. Ids must be unique across the whole tree (a dev warning names duplicates). A non-string label needs textValue for filtering and type-ahead.

  • selectionModedefault "single"
    "none" | "single" | "multiple"

    none: rows only open and act. single: one selected row. multiple: clicks toggle rows, Shift extends a range, Ctrl/Cmd+A selects every visible row.

  • selectedKeys
    string[]

    Selected ids (controlled). Pair with onSelectedKeysChange.

  • defaultSelectedKeys
    string[]

    Selected ids while uncontrolled. Default [].

  • onSelectedKeysChange
    (keys: string[]) => void

    Fires with the new selection, in display order.

  • checkboxesdefault false
    boolean

    Tri-state checkboxes before the icons, independent of selection: the box checks, the row selects.

  • checkedKeys
    string[]

    Checked ids (controlled). With cascade, fully checked parents are part of the keys too.

  • defaultCheckedKeys
    string[]

    Checked ids while uncontrolled. Default [].

  • onCheckedKeysChange
    (keys: string[]) => void

    Fires with the new checked ids.

  • cascadedefault true
    boolean

    Checking a parent checks its (loaded, enabled) descendants; a parent is checked when all of them are and shows a dash when some are. Disabled rows are skipped. Needs checkboxes.

  • expandedKeys
    string[]

    Open folder ids (controlled). Ids of nodes that do not exist are ignored.

  • defaultExpandedKeys
    string[]

    Open folder ids while uncontrolled. Default [].

  • onExpandedKeysChange
    (keys: string[]) => void

    Fires when a folder opens or closes, including the folders a filter opens.

  • defaultExpandAlldefault false
    boolean

    Starts with every folder that has children open (lazy folders stay closed).

  • expandOnClickdefault true
    boolean

    Clicking a folder row also opens or closes it, like a file explorer. Off: only the chevron and the keyboard do.

  • loadChildren
    (node: TreeNode) => Promise<TreeNode[]>

    Lazy children: a node with hasChildren and no children loads on first expand, with a spinner row; a rejected promise shows a Retry row. Results are cached inside the tree. Filtering only sees loaded nodes.

  • onAction
    (node: TreeNode) => void

    Enter, double click or a tap on a leaf: open the file. Without it a node with href follows its link.

  • searchabledefault false
    boolean

    A filter field above the tree (16px text on phones, Escape clears it, ArrowDown enters the tree).

  • filterdefault ""
    string

    The filter text (controlled, pair with onFilterChange); works without the field too. Case and diacritics never matter, matches keep their ancestors and open the path to them, debounced 150ms.

  • onFilterChange
    (filter: string) => void

    Fires on every keystroke in the filter field.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Rows 28 / 32 / 36px with 13 / 14 / 15px text; 44px on touch screens.

  • indentdefault 16
    number

    Pixels per level.

  • guidesdefault true
    boolean

    Vertical indent guides; the guide of the focused row's branch is darker.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The selected row (neutral is a gray tint, other tones their soft fill and text) and the checkboxes.

  • colordefault ""
    string

    Any CSS color; replaces the tone for the selected row and the checkboxes.

  • folderIconsdefault false
    boolean

    Folder / open folder icons for parents and a file icon for leaves, for nodes without their own icon.

  • draggabledefault false
    boolean

    Drag rows (long press on touch) to reorder them or drop them into folders, and Alt + arrows to move with the keyboard. Needs onMove.

  • onMove
    (event: TreeMoveEvent) => void

    { id, targetId, position: "before" | "after" | "inside", fromParentId, toParentId, index }. index is the position among the new siblings; moveTreeNode(items, event) applies it to your data.

  • virtualizedefault "auto"
    boolean | "auto" | "always" | "never"

    auto renders only the rows in view above 300 visible rows; true / always and false / never force it. Virtualized trees are flat (aria-level, aria-setsize, aria-posinset), skip the expand animation and default to a 420px scroll box.

  • maxHeightdefault 0
    number | string

    Makes the tree its own scroll box (px or any CSS length). 0 grows with the rows.

  • emptyTextdefault "No items"
    string

    Title of the empty state shown in place of the tree when items is empty. A filter without matches shows labels.noResults with a Clear filter button instead.

  • emptyState
    ReactNode

    Replaces the built-in empty state (an EmptyState with emptyText as its title) when items is empty.

  • labels
    Partial<TreeViewLabels>

    Strings: tree (accessible name), search, noResults, clearFilter, loading, loadError, retry, actions, cancel (the phone sheet), and the move announcements pickedUp, dropBefore, dropAfter, dropInside, notAllowed, dropped, cancelled, moved ({item}, {target}, {position}, {total}). English defaults in DEFAULT_TREE_VIEW_LABELS.

  • classNames
    Partial<Record<"search" | "viewport" | "root" | "item" | "row" | "guides" | "chevron" | "checkbox" | "icon" | "label" | "description" | "badge" | "actions" | "group" | "loading" | "error" | "empty" | "overlay", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the wrapper, merged last.

Parts

  • flattenTree

    function

    Depth-first rows with level, parentId, ancestors, index and setSize; with expandedKeys only the visible ones. Signature: (items: TreeNode[], expandedKeys?: Iterable<string>) => FlatTreeNode[].

  • findNode

    function

    Finds a node anywhere in the tree. Signature: (items: TreeNode[], id: string) => TreeNode | undefined.

  • getAncestorIds

    function

    The parent chain of a node, top level first. Handy for expandedKeys that reveal a node. Signature: (items: TreeNode[], id: string) => string[].

  • getCheckedState

    function

    The cascade state of a node, the same rule the checkboxes use. Signature: (node: TreeNode, checkedKeys: Iterable<string>) => boolean | "indeterminate".

  • moveTreeNode

    function

    Applies an onMove event to your data and returns new items; untouched branches keep their identity. Signature: (items: TreeNode[], event: TreeMoveEvent) => TreeNode[].

  • DEFAULT_TREE_VIEW_LABELS

    constant

    The English strings for the filter, no matches, loading and retry, the row actions menu and the drag and drop announcements. Override keys through labels.

  • TreeNode

    type

    One node of items: an id, a label and optional children, plus the per-node extras described under items.

  • TreeMoveEvent

    type

    What onMove receives after a drop: { id, targetId, position ("before", "after" or "inside"), fromParentId, toParentId, index }.

  • TreeViewProps

    type

    The props of TreeView, for wrappers that pass them through.

Examples

Permissions with cascade

Checking a group checks everything under it; partly checked groups show a dash. Disabled rows are skipped.

Searchable categories

Matches keep their parents, open the path to them and bold the matched letters.

Drag to move files

Drop before, after or into a folder; onMove plus moveTreeNode updates your data. A folder opens if you hover it.

5,000 rows, virtualized

Above 300 visible rows only what is in view renders; maxHeight makes the tree its own scroll box.

Compact sidebar

size="sm", no guides, a brand tone for the selected row and icons from the data.

Lazy children with retry

Children load on first expand. The FTP folder fails once, then Retry loads it.

Dependencies

@dnd-kit/core@tanstack/react-virtualmotionlucide-reactbuttoncheckboxdropdown-menuempty-stateinputspinneruse-controllable-stateuse-debounceuse-selectionmotiontext-matchtonesui-config

Files

  • components/ui/tree-view.tsx

FAQ

Is the React Tree View component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Tree View?

Run npx wingo-ui@latest add tree-view. It also adds the Wingo UI items it builds on (Button, Checkbox, Dropdown Menu, Empty State, Input and 8 more) and the npm packages @dnd-kit/core, @tanstack/react-virtual, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/tree-view.

How does Tree View behave on phones?

Double click a file (or tap it on a phone) to open it. On a phone rows grow to 44px, the ⋯ button is always visible and opens a bottom sheet, the whole row is the tap target and the filter field uses 16px text.

See also

AccordionSidebarListCheckboxListbox

Used in

File managerTree SelectFile System

More React data display components

KbdBadgeCountdownApproval FlowAvatarCalendar View
All 23 React data display components

Articles using this component

  • React File Manager: Folders, Uploads and the Backend API

    Component guides · Nov 6, 2026 · 10 min read

  • React Tree View: Checkboxes, Lazy Loading, Drag to Move

    Component guides · Oct 19, 2026 · 10 min read

PreviousTimelineNext Audit Log
DocumentsNorthwind CRM · 16 files
1 selected
  • Clients7
    • Tech Solutions Inc.5
      • Contracts2
      • Invoices 20263
        • Invoice 2026-0412.pdf
        • Invoice 2026-0398.pdf
        • Invoice 2026-0371.pdf
    • Summit Logistics3
    • Bennett & Partners2
  • Reports3
  • Templates2
  • Archive 2025read only1

Double click, Enter or a tap opens a file; arrows move, letters jump