Component Trees
Introduction
The Trees component allows you to display interactive file and folder trees directly in your Docusaurus pages. Perfect for documenting project structure, explaining architecture, or presenting code examples.
Project with Icons
✨ Key Features
- Intuitive syntax: Use natural nested JSX tags
- Collapsible folders: Click to expand/collapse folders
- Two display modes:
- Native emojis by default (📁 📄)
- Custom icons via LogoIcon (Iconify)
- Dark mode: Full dark theme support
- Accessible: Keyboard navigation and smooth animations
- Optional badges: Add extra information
Installation
Copy/Create Files
Create the following folder structure in your Docusaurus project:
(Note that I'm intentionally not using my component here, ha ha!)
src/components/Trees/
├── index.js # Main Trees component
├── Folder/
│ └── index.js # Folder component
├── File/
│ └── index.js # File component
├── utils/
│ └── TreeItem.js # Shared internal component
└── styles.module.css # CSS styles
The component follows a modular architecture inspired by React best practices
Component Architecture
File Trees/index.js
Main Component
- Tree container
- Displays an optional title
- Manages children rendering (Folder/File)
- Applies initial indentation level (0)
import React, { Children, isValidElement } from "react";
import PropTypes from "prop-types";
import styles from "./styles.module.css";
/**
* Trees Component - Main component to display a tree structure
* Uses intuitive JSX syntax with nested tags
*/
export default function Trees({ title, children }) {
return (
<div className={styles.treeContainer}>
{title && <h3 className={styles.treeTitle}>{title}</h3>}
<div className={styles.treeContent}>
{Children.map(children, (child, index) => {
if (isValidElement(child)) {
return React.cloneElement(child, {
key: index,
level: 0,
});
}
return null;
})}
</div>
</div>
);
}
Trees.propTypes = {
/** Optional tree title */
title: PropTypes.string,
/** Tree content (Folder and File components) */
children: PropTypes.node.isRequired,
};
Trees/Folder/index.js
Folder Component
- Represents a folder in the tree
- Wrapper around
TreeItem
withdefaultEmoji="📁"
- Can contain children (sub-folders and files)
- Props:
label
,icon
,expanded
,badge
,iconSize
import React from "react";
import PropTypes from "prop-types";
import TreeItem from "../utils/TreeItem";
/**
* Folder Component - Represents a folder in the tree structure
*/
export default function Folder({ icon, iconSize = 24, badge, label, expanded = false, children, level = 0 }) {
return (
<TreeItem
icon={icon}
iconSize={iconSize}
badge={badge}
label={label}
expanded={expanded}
level={level}
defaultEmoji="📁"
>
{children}
</TreeItem>
);
}
Folder.propTypes = {
/** Icon name (LogoIcon) to display */
icon: PropTypes.string,
/** Icon size in pixels */
iconSize: PropTypes.number,
/** Optional badge to display */
badge: PropTypes.string,
/** Folder name to display */
label: PropTypes.string.isRequired,
/** Initial state: expanded (true) or collapsed (false) */
expanded: PropTypes.bool,
/** Folder content (sub-folders and files) */
children: PropTypes.node,
/** Nesting level (managed automatically) */
level: PropTypes.number,
};
Trees/File/index.js
File Component
- Represents a file in the tree
- Wrapper around
TreeItem
withdefaultEmoji="📄"
- Cannot contain children
- Props:
label
,icon
,badge
,iconSize
import React from "react";
import PropTypes from "prop-types";
import TreeItem from "../utils/TreeItem";
/**
* File Component - Represents a file in the tree structure
*/
export default function File({ icon, iconSize = 20, badge, label, level = 0 }) {
return (
<TreeItem
icon={icon}
iconSize={iconSize}
badge={badge}
label={label}
expanded={false}
level={level}
defaultEmoji="📄"
/>
);
}
File.propTypes = {
/** Icon name (LogoIcon) to display */
icon: PropTypes.string,
/** Icon size in pixels */
iconSize: PropTypes.number,
/** Optional badge to display */
badge: PropTypes.string,
/** File name to display */
label: PropTypes.string.isRequired,
/** Nesting level (managed automatically) */
level: PropTypes.number,
};
File Trees/utils/TreeItem.js
Internal Utility Component
Shared component that factors all common logic:
- ✅ Expansion state management (
useState
) - ✅ Valid React children detection
- ✅ Icon display (LogoIcon or native emojis)
- ✅ Recursive sub-element rendering
- ✅ Dynamic indentation calculation (
level * 20px
) - ✅ User interaction (click to expand/collapse)
Icon display logic:
- If
icon
is provided → Uses LogoIcon (Iconify) - If
icon
is absent → UsesdefaultEmoji
(📁 or 📄) - If
icon={null}
→ No icon
import React, { useState, Children, isValidElement } from "react";
import styles from "../styles.module.css";
import LogoIcon from "../../LogoIcon";
/**
* TreeItem Component - Represents an individual tree element
* Internal component shared by Folder and File
*/
export default function TreeItem({ icon, iconSize = 24, badge, label, expanded = false, children, level = 0, defaultEmoji }) {
const [isExpanded, setIsExpanded] = useState(expanded);
// Filter valid React children (other Folder or File)
const childElements = Children.toArray(children).filter(child => isValidElement(child));
const hasChildren = childElements.length > 0;
const toggleExpand = () => {
if (hasChildren) {
setIsExpanded(!isExpanded);
}
};
return (
<div className={styles.treeNode}>
<div
className={`${styles.nodeContent} ${hasChildren ? styles.hasChildren : ""}`}
style={{ paddingLeft: `${level * 20}px` }}
onClick={toggleExpand}
>
{/* Expand/collapse icon */}
{hasChildren && (
<span className={styles.expandIcon}>
{isExpanded ? "▼" : "▶"}
</span>
)}
{/* Node icon with label (via LogoIcon or native emoji) */}
{icon ? (
<span className={styles.nodeIcon}>
<LogoIcon name={icon} size={iconSize} />
<span className={styles.nodeLabel}>{label}</span>
</span>
) : defaultEmoji ? (
<span className={styles.nodeIcon}>
<span style={{ fontSize: `${iconSize}px`, lineHeight: 1 }}>{defaultEmoji}</span>
<span className={styles.nodeLabel}>{label}</span>
</span>
) : (
<span className={styles.nodeLabel}>{label}</span>
)}
{/* Optional badge */}
{badge && (
<span className={styles.nodeBadge}>{badge}</span>
)}
</div>
{/* Children (recursive) */}
{hasChildren && isExpanded && (
<div className={styles.nodeChildren}>
{childElements.map((child, index) =>
React.cloneElement(child, {
key: index,
level: level + 1,
})
)}
</div>
)}
</div>
);
}
File styles.module.css
Component CSS Styles File
Single CSS shared by all components with:
- CSS variables for colors and spacing
- Dark mode support (
[data-theme='dark']
) - Smooth animations for transitions
- Modular classes (
.treeContainer
,.nodeContent
, etc.)
.treeContainer {
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 3px;
padding: 16px;
margin: 20px 0;
background-color: var(--ifm-background-surface-color);
}
.treeTitle {
margin: 0 0 16px 0;
font-size: 1.2rem;
font-weight: 600;
color: var(--ifm-color-primary);
border-bottom: 2px solid var(--ifm-color-emphasis-300);
padding-bottom: 8px;
}
.treeContent {
font-family: var(--ifm-font-family-monospace);
}
.treeNode {
margin: 0;
position: relative;
}
.nodeContent {
display: flex;
align-items: center;
padding: 6px 8px;
padding-left: 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
cursor: default;
position: relative;
}
.nodeContent.hasChildren {
cursor: pointer;
}
.nodeContent:hover {
background-color: var(--ifm-color-emphasis-100);
}
.expandIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
min-width: 20px;
height: 20px;
margin-right: 4px;
font-size: 10px;
color: var(--ifm-color-emphasis-600);
transition: transform 0.2s ease;
user-select: none;
flex-shrink: 0;
}
.nodeIcon {
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1;
gap: 8px;
}
/* Add offset for files (without expandIcon) for alignment */
.nodeContent:not(.hasChildren) .nodeIcon {
margin-left: 24px;
}
.nodeLabel {
font-size: 0.95rem;
color: var(--ifm-color-content);
user-select: none;
line-height: 1;
white-space: nowrap;
}
.nodeBadge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 0.75rem;
font-weight: 600;
border-radius: 12px;
background-color: var(--ifm-color-primary);
color: white;
user-select: none;
}
.nodeChildren {
margin-left: 20px;
border-left: 1px solid var(--ifm-color-emphasis-300);
padding-left: 8px;
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Dark mode styles */
[data-theme='dark'] .treeContainer {
border-color: var(--ifm-color-emphasis-300);
background-color: var(--ifm-background-surface-color);
}
[data-theme='dark'] .nodeContent:hover {
background-color: var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .nodeLabel {
color: var(--ifm-color-content);
}
/* Responsive */
@media (max-width: 768px) {
.treeContainer {
padding: 12px;
}
.nodeLabel {
font-size: 0.9rem;
}
.nodeIcon {
transform: scale(0.9);
}
}
MDX Integration
Register in MDXComponents
Open the file src/theme/MDXComponents.js
and add the imports:
import React from 'react';
import MDXComponents from '@theme-original/MDXComponents';
import Trees from '@site/src/components/Trees';
import Folder from '@site/src/components/Trees/Folder';
import File from '@site/src/components/Trees/File';
export default {
...MDXComponents,
Trees,
Folder,
File
};
Use in Your MDX Files
Once registered in MDXComponents, the components are automatically available in all your .mdx
files:
See official documentation
## Project Structure
<Trees title="My Project">
<Folder label="src">
<File label="index.js" />
<File label="App.jsx" />
</Folder>
<File label="package.json" />
</Trees>
Usage Examples
Example 1: Simple Tree with Native Emojis
Without LogoIcon icons, the component automatically uses native emojis (📁 for folders, 📄 for files).
Simple Project
<Trees title="Simple Project">
<Folder label="src" expanded={true}>
<File label="index.js" />
<File label="App.jsx" />
</Folder>
<Folder label="public" expanded={true}>
<File label="index.html" />
</Folder>
<File label="package.json" />
<File label="README.md" />
</Trees>
Example 2: Collapsed Folders by Default
Use the expanded={false}
prop to have folders collapsed on load.
Compact Tree
<Trees title="Compact Tree">
<Folder label="node_modules" expanded={false}>
<Folder label="react">
<File label="index.js" />
</Folder>
</Folder>
<Folder label="src">
<File label="App.jsx" />
</Folder>
</Trees>
Example 3: With LogoIcon Icons (Simple)
Add the icon
prop to use Iconify icons via LogoIcon.
Project with Icons
<Trees title="Project with Icons">
<Folder label="src" icon="unjs:mkdist">
<File label="index.js" icon="logos:javascript" />
<File label="App.jsx" icon="logos:react" />
<File label="styles.css" icon="logos:css-3" />
</Folder>
<File label="package.json" icon="vscode-icons:file-type-node" />
</Trees>
Example 4: Complex Tree with Badges
Use the badge
prop to display additional information.
Backend Architecture
<Trees title="Backend Architecture">
<Folder label="src" icon="vscode-icons:default-folder">
<Folder label="controllers" icon="vscode-icons:folder-type-controller" badge="5 files">
<File label="userController.js" icon="vscode-icons:file-type-js" />
<File label="authController.js" icon="vscode-icons:file-type-js" />
</Folder>
<Folder label="models" icon="vscode-icons:folder-type-model" badge="3 files">
<File label="User.js" icon="vscode-icons:file-type-js" />
</Folder>
<File label="server.js" icon="vscode-icons:file-type-node" badge="main" />
</Folder>
<File label=".env" icon="vscode-icons:file-type-dotenv" />
</Trees>
Example 5: Complete Docusaurus Project
Realistic example of a Docusaurus structure.
Docusaurus Project
<Trees title="Docusaurus Project">
<Folder label="blog" icon="vscode-icons:folder-type-docs">
<Folder label="2025" icon="vscode-icons:default-folder">
<File label="index.mdx" icon="vscode-icons:file-type-mdx" />
</Folder>
</Folder>
<Folder label="src" icon="vscode-icons:default-folder">
<Folder label="components" icon="vscode-icons:folder-type-component">
<Folder label="Trees" icon="vscode-icons:default-folder" badge="new">
<File label="index.js" icon="vscode-icons:file-type-reactjs" />
</Folder>
</Folder>
</Folder>
<File label="docusaurus.config.js" icon="vscode-icons:file-type-docusaurus" />
</Trees>
Component Props
Component <Trees>
Prop | Type | Default | Description |
---|---|---|---|
title | string | - | Optional tree title |
children | node | - | Content (Folder and File components) |
Component <Folder>
Prop | Type | Default | Description |
---|---|---|---|
label | string | ❌ Required | Folder name |
icon | string | - | Iconify icon name (via LogoIcon) |
iconSize | number | 24 | Icon size in pixels |
badge | string | - | Optional badge to display |
expanded | boolean | false | Initial state (expanded/collapsed) |
children | node | - | Sub-folders and files |
Component <File>
Prop | Type | Default | Description |
---|---|---|---|
label | string | ❌ Required | File name |
icon | string | - | Iconify icon name (via LogoIcon) |
iconSize | number | 20 | Icon size in pixels |
badge | string | - | Optional badge to display |
Usage With and Without LogoIcon
Mode 1: Native Emojis (without LogoIcon)
Advantages:
- ✅ No external dependencies
- ✅ Works immediately
- ✅ Lightweight and performant
<Trees title="Simple">
<Folder label="Documents">
<File label="report.pdf" />
</Folder>
</Trees>
Result: 📁 Documents / 📄 report.pdf
Mode 2: LogoIcon Icons (with Iconify)
Prerequisites: Have the LogoIcon component installed in your project.
Advantages:
- ✅ Specific icons by file type
- ✅ Over 150,000 icons available
- ✅ Professional rendering
<Trees title="With Icons">
<Folder label="src" icon="vscode-icons:default-folder">
<File label="App.jsx" icon="vscode-icons:file-type-reactjs" />
</Folder>
</Trees>
Mode 3: No Icon at All
Pass icon={null}
to completely disable icons.
<File label="file.txt" icon={null} />
Dark Mode Support
The component automatically supports Docusaurus dark mode through CSS variables:
/* Light mode */
.nodeContent {
background-color: var(--ifm-background-surface-color);
color: var(--ifm-font-color-base);
}
/* Dark mode (automatic) */
[data-theme='dark'] .nodeContent:hover {
background-color: rgba(255, 255, 255, 0.05);
}
Best Practices
✅ Do
- Use
expanded={false}
for large folders (e.g.,node_modules
) - Add badges for important information
- Use consistent icons (same Iconify pack)
- Limit depth to 4-5 levels maximum
❌ Don't
- Don't create overly deep trees (readability)
- Avoid mixing native emojis and LogoIcon in the same tree
- Don't overuse badges (visual pollution)
Conclusion
The Trees component offers a simple and flexible solution for displaying trees in Docusaurus:
- Two modes: native emojis or custom icons
- Interactive: collapsible/expandable folders
- Dark mode natively supported
- Accessible and performant
- Modular architecture easy to maintain
Perfect for:
- Documenting project structures
- Presenting file architectures
- Creating installation guides
- Illustrating code examples
Try it in your next article and let me know your feedback! 🚀
This article is part of the Design your site series:
Loading comments…
Related posts

Component Skill Bars & Circles
September 16, 2025