It’s a dependency-free custom element, so you can drop it into any project/framework or not and get a functional file tree.
It can be used to display nested files and folders, handle user interactions like clicks and right-clicks, and even load folder contents asynchronously.
<file-tree> tag is all you need to get started.1. Install the file-tree package via npm:
# NPM $ npm install @webreflection/file-tree
2. Import it into your project and create a file tree programmatically:
import { Tree, Folder, File } from './prod.js'; // Create a new tree instance
const tree = new Tree;
// Add it to your page
document.body.appendChild(tree);
// Add some files and folders
const dist = new Folder('dist');
dist.append(new File([], 'prod.js'));
tree.append(dist, new File([], 'README.md')); 3. You can also initialize the tree directly from your HTML structure. Here’s a full example:
<file-tree>
<ul>
<li class="file" data-bytes="1000"><button>a_file.txt</button></li>
<li class="file"><button>file.bin</button></li>
<li class="folder">
<button>folder</button>
<ul>
<li class="file"><button data-details="file.txt">file.txt</button></li>
<li class="folder opened">
<button>folder</button>
<ul>
<li class="file" data-type="text/plain"><button>file.txt</button></li>
<li class="text"><button>file.txt</button></li>
<li class="file"><button>file.svg</button></li>
<li class="file"><button>file.png</button></li>
</ul>
</li>
<li class="folder">
<button>folder</button>
<ul>
<li class="file"><button>file.md</button></li>
<li class="file"><button>file.txt</button></li>
<li class="file"><button>file.txt</button></li>
<li class="file"><button>file.txt</button></li>
<li class="folder opened">
<button>folder</button>
<ul>
<li class="file" data-type="text/plain"><button>file.txt</button></li>
<li class="text"><button>file.txt</button></li>
<li class="file"><button>file.txt</button></li>
<li class="file"><button>file.txt</button></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="file"><button>file.txt</button></li>
<li class="file"><button>file.txt</button></li>
</ul>
</file-tree> <script type="module">
import { Tree, File, Folder } from './prod.js';
document.querySelector('file-tree').addEventListener('click', event => {
const { action, folder, owner, path, target } = event.detail;
console.log({ action, folder, owner, path, target });
});
const tree = new Tree;
document.body.appendChild(tree);
const a = new Folder('a');
const b = new Folder('b');
a.append('b.txt');
b.append('b.txt', 'a.txt');
const c = new File([], 'c.txt', { type: 'text/plain' });
globalThis.a = a;
globalThis.b = b;
globalThis.c = c;
globalThis.tree = tree;
tree.append(a, c, b, { name: 'n.txt' });
tree.addEventListener('contextmenu', event => {
const { action, folder, path, target } = event.detail;
console.log({ action, folder, path, target });
event.preventDefault();
});
tree.addEventListener('click', event => {
const { action, folder, path, target } = event.detail;
if (folder) {
if (action === 'open') {
event.waitUntil(new Promise(resolve => setTimeout(resolve, Math.random() * 1000)));
}
}
else {
console.log(path);
}
});
// setTimeout(() => tree.rename(c), 1000);
</script> The Tree class is the main component you’ll interact with. It extends HTMLElement, so you can use it as <file-tree> in your HTML or instantiate it with new Tree(). It also implements the full Folder interface, meaning you can add items directly to the root of the tree.
selected: Item | null: A read-only property that returns the last selected File or Folder instance, or null if nothing is selected.items: Item[]: An array containing all the top-level File and Folder instances within the tree. This is an alias for the files property.append(...items: (string | object | Item)[]): this: Adds one or more items to the root of the tree. You can pass File or Folder instances, plain objects like { name: 'new-file.txt' }, or just a string for a simple file.remove(...items: (Item | string)[]): this: Removes one or more items from the tree. You can pass the actual Item instance or a string path like 'src/components'.rename(item: Item | string, name?: string): Item | Promise<Item>: Renames a given item. If name is not provided, it will prompt the user for a new name. You can pass the Item instance or its path.update(file: File | string, content: Content | Content[]): File: Updates the content of a specified file. You can identify the file by its instance or by its string path.query(path: string): Item[] | null: A utility method to retrieve all items in a given path. It returns a flat array of Item instances leading to the target, or null if the path is not found. For example, tree.query('src/main.js') would return [srcFolder, mainJsFile].The Tree component dispatches click and contextmenu events. You can listen for them using tree.addEventListener(). The event’s detail object contains a rich payload:
| Property | Type | Description |
|---|---|---|
action | "open" | "close" | "click" | The specific action performed (open/close for folders, click for files). |
folder | boolean | true if the item is a folder, false otherwise. |
originalTarget | HTMLLIElement | The actual <li> DOM element that was clicked. |
owner | Folder | The parent folder that contains the item. |
path | string | The full, web-compatible path to the item (e.g., src/components/Button.jsx). |
target | File | Folder | The File or Folder instance that the event concerns. |
The Folder class represents a directory within the file tree. It’s the primary way to organize items hierarchically.
name: string: The name of the folder.type: "folder": A read-only property that always returns "folder".size: number: The total size in bytes of all files contained within the folder, calculated recursively.items: Item[]: An array of all File and Folder instances directly inside this folder. This is an alias for files.The Folder class shares most of its methods with Tree for a consistent API:
append(...items: (string | object | Item)[]): this: Adds items inside this folder.remove(...items: Item[]): this: Removes items from this folder.rename(item: Item, name?: string): Item | Promise<Item>: Renames a direct child item.update(file: File, content: Content | Content[]): File: Updates the content of a direct child file.The File class extends the browser’s native File object, adding some convenience. It represents a single file in the tree.
new File(content, name, options?)
content: Content | Content[]: The file’s content. This can be a string, a Blob, or an ArrayBuffer.name: string: The name of the file. The library automatically sanitizes the name to remove invalid characters.options?: { type?: string }: An optional object to override the MIME type. If not provided, the type is inferred from the file extension.// A simple text file. The type 'text/markdown' is inferred.
const readme = new File(['# My Project'], 'README.md');
// A binary file where we explicitly set the type.
const image = new File(['...binary data...'], 'logo.png', { type: 'image/png' });
// A file with a custom type.
const config = new File(['{"key": "value"}'], 'config.json', {
type: 'application/json'
}); When a user clicks to open a folder, you can fetch its contents from a server. The event.waitUntil() method is designed for this. You pass it a Promise, and the UI will automatically display a loading indicator on the folder until the promise resolves.
tree.addEventListener('click', (event) => {
const { action, folder, target } = event.detail;
if (action === 'open' && folder) {
// If the folder is empty, fetch its content
if (target.items.length === 0) {
event.waitUntil(
fetchFolderContent(target.name).then(content => {
// Assuming content is an array of File and Folder instances
target.append(...content);
})
);
}
}
}); Q: Can I customize the visual appearance of the file tree?
A: Yes, the component uses standard CSS classes that you can style. The library generates semantic HTML with .file, .folder, and .opened classes. You can override these styles or add custom CSS to match your design requirements.
Q: How do I handle large directory structures without performance issues?
A: Use the async loading pattern with event.waitUntil() to load folder contents on demand. This prevents initial rendering bottlenecks and provides better user experience. Load only the immediate children when folders are expanded rather than the entire tree structure upfront.
Q: Can I use this with React or Vue?
A: Yes. It’s a standard Custom Element, so it works in any framework that supports them. You can use it in your JSX or Vue templates just like any other HTML tag. You’ll just need to handle event listeners according to your framework’s syntax.
Q: How do I handle right-click to show a custom context menu?
A: You listen for the contextmenu event. In your event handler, you can display your own menu and then call event.preventDefault() to stop the browser’s default context menu from appearing. The event.detail object will give you the target file or folder that was right-clicked.
The post Modern File Tree Web Component with Async Loading – file-tree appeared first on CSS Script.
Today: The appraisal of SARL.com / LimonYSalVentura.com sold for $8,186 / Evaluating EmailField.com and More……
Barbara Simmons serves as executive director of The Peace Center, an educational peace and justice…
Over the last 48 hours, something different has been unfolding on X & LinkedIn –…
Mario Day, or "MAR10 Day," is back again this year, and Nintendo is kicking off…
Today's links The web is bearable with RSS: And don't forget "Reader Mode." Hey look…
Artificial Intelligence Watershed Moment for AI–Human Collaboration in MathBenjamin Skuse | IEEE Spectrum “The 8-dimensional…
This website uses cookies.