React Tree Folder

file with default icon
unicode icon
Hireact element as icon
with content
file
works on a file too
Read The Docs

Installation

npm i react-tree-folder # or bun, or pnpm

Simple Usage

import { TreeFolder, type Tree } from 'react-tree-folder'

// import this in your global layout
// if you use this on multiple pages
import 'react-tree-folder/dist/style.css' 

const tree : Tree = [
  {
    text: 'Some Folder',
    dir: true,

    // a directory may be open by default
    open: true,

    // a directory may contain
    // other directories or files
    branch: [
      {
        text: 'Sub Folder',
        dir: true,
      },
      {
        text: 'Sub File',
      }
    ]
  },
  {
    text: 'Some File',
  }
]

export function SimpleUsageExample(){
  return <TreeFolder tree={tree} />
}

Simple Usage Result

Sub File
Some File

Types


export type Branch = {
  
  text: string
  open?: boolean
  dir?: boolean

  // custom icon
  // for files
  icon?: ReactNode 

  // custom icon
  // for folder
  folderIcon?: {
    closed:ReactNode
    open: ReactNode
  }
  
  // using onClick will render a button
  onClick?: () => void

  // using link will render an anchor
  // using link and onClick, 
  // will also render an anchor
  link?:{
    href: string,
    newTab?: boolean
  }

  // custom component
  component?: (text: string) => ReactNode
  
  branch?: Tree

  // coloring works individualy
  color?: string
  hoverColor?: string

}

export type Tree = Branch[]

// without icons
// import { TreeFolder } from 'react-tree-folder/dist/no-icons'
export type ReactTreeFolderProps = { 
  tree: Tree 
  iconFolderOpen: ReactNode
  iconFolder: ReactNode
  iconFile?: ReactNode

  // coloring works globaly
  color?: string
  hoverColor?: string
  borderColor?: string

  containerClass?: string
}

// with default icon
// import { TreeFolder } from 'react-tree-folder'
// this is the one we are using in the example above
export type ReactTreeWithIconsProps = {
  tree: Tree 
  color?: string

  // coloring works globaly
  hoverColor?: string
  borderColor?: string

  containerClass?: string
}

Custom Icon

// Customize how the file/dir icons look like
const treeFolder:Tree = [
  {
    text: 'default icon'
  },
  {
    text: 'unicode icon',
    icon: '⚽'
  },
  {
    text: 'react element as icon',
    icon: <i style={{color:'orange', marginRight: '5px'}}>Hi</i>
  },
  {
    text: 'custom folder icon',
    dir: true,
    folderIcon: {
      closed: '📁',
      open: '📂'
      // a react element will also work
    }
  },
]

export function CustomFileIcons() {
  return (
    <TreeFolder tree={treeFolder} />
  );
}

Custom Icons Result

default icon
unicode icon
Hireact element as icon

Custom Component

import { TypeAnimation } from 'react-type-animation';

function MyAnimatedText({ text }:{ text: string }){

  return <TypeAnimation
    sequence={[
      'One', // Types 'One'
      1000, // Waits 1s
      'Two', // Deletes 'One' and types 'Two'
      2000, // Waits 2s
      text, // Types text
      3000 // wait 3s
    ]}
    wrapper="span"
    cursor={true}
    repeat={Infinity}
  />

}

const treeFolder:Tree = [
  {
    text: 'aFile',
    component: (text) => <MyAnimatedText text={text} />
  },
  {
    text: 'aButtonFile',
    component: (text) => <MyAnimatedText text={text} />,
    onClick: () => alert('Hey, you!')
  },
  {
    text: 'aDir',
    component: (text) => <MyAnimatedText text={text} />,
    dir: true,
    open: true,
    branch: [{ text: 'just a regular file' }]
  },
]

export function CustomComponent() {
  return (
    <TreeFolder tree={treeFolder} />
  );
}

Custom Component Result

just a regular file

Custom Colors

const treeFolder:Tree = [
  {
    text: 'custom color',
    dir: true,
    branch: [{ text: 'content' }],

    // coloring for specific item
    color: 'greenyellow',
    hoverColor: 'lightskyblue',
  },

  {
    text: 'file with custom color',
    color: 'greenyellow',

    // note the hover don't work
    // since this is not a button 
    // (no onClick attribute)
    hoverColor: 'lightskyblue',
  },

  {
    text: 'global color',
    dir: true,
    branch: [{ text: 'content' }],
  },
  {
    text: 'file',
  },

]

export function CustomColors() {
  return (
    <TreeFolder 
      tree={treeFolder} 
      
      // coloring for all item
      color='hotpink'
      hoverColor='yellow'

      // to color the side-line
      borderColor='cyan'


    />
  );
}

Custom Colors Result

content
file with custom color
content
file

OnClick Handler

const treeFolder:Tree = [
  {
    text: 'on a file',
    onClick: () => alert('Clicked on a file!')
  },
  {
    text: 'on a folder',
    onClick: () => alert('Clicked on a folder!'),
    dir: true,
    branch: [{ text: 'folder content' }]
  }
]

export function OnClickHandler() {
  return (
    <TreeFolder tree={treeFolder} />
  );
}

Onclick Handler Result

folder content
const treeFolder:Tree = [
  {
    text: 'link',
    link: {
      href: 'https://asdf.com',

      // optional
      newTab: true
    },

    // also optional
    onClick: () => alert('this is a link')
  },
]

export function ALink() {
  return (
    <TreeFolder tree={treeFolder}  />
  );
}

Container Class

import { css } from '@emotion/css'

const borderedBox = css`
  border-radius: 1rem;
  border: 1px solid #42b342;
  padding: 1rem;
`

const treeFolder:Tree = [
  {
    text: 'folder',
    dir: true,
    branch: [{ text: 'content' }]
  },
  {
    text: 'file',
    icon: '🥧'
  },
]

export function ContainerClass() {
  return (
    <TreeFolder 
      tree={treeFolder} 
      containerClass={borderedBox} />
  );
}

Container Class Result

content
🥧file

No Icons Variant

// use the no-icons variant
// when you want to use custom icon
// right from the start
import { TreeFolder, type Tree } from 'react-tree-folder/dist/no-icons'
import SvgFileIcon from './svg-file-icon'

const treeFolder:Tree = [
  {
    text: 'open this folder',
    dir: true,
    branch: [{ text: 'this is a file '}]
  }
]

export function CustomIcons(){

  return <TreeFolder 
    tree={treeFolder}
    iconFolder={'📁'}
    iconFolderOpen={'📂'}
    iconFile={<SvgFileIcon />}
  />

}

Custom Icon Result

this is a file

File Sizes

vite v5.2.13 building for production...
 6 modules transformed.

[vite:dts] Start generate declaration files...
dist/style.css         1.02 kB gzip: 0.46 kB
dist/no-icons.js       0.11 kB gzip: 0.13 kB map: 0.09 kB
dist/tree-mHxiewAB.js  3.24 kB gzip: 0.99 kB map: 7.00 kB
dist/index.js          4.09 kB gzip: 1.10 kB map: 5.55 kB
[vite:dts] Declaration files built in 409ms.

dist/style.css          1.02 kB gzip: 0.46 kB
dist/no-icons.cjs       0.19 kB gzip: 0.18 kB map: 0.09 kB
dist/tree-CMVc77ZW.cjs  1.92 kB gzip: 0.78 kB map: 6.45 kB
dist/index.cjs          3.43 kB gzip: 1.05 kB map: 5.20 kB
 built in 510ms

Last built locally. The packages built in CI may result in similar sizes.

- // -