with content
file
works on a file too
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
Link
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}  />
  );
}
Link Result
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.