Tree
Nested folders and files. A folder is a details, so
no JS is needed.
Usage, with a search field
An input[type="search"] inside
.tree filters files as you type. Matching files stay
visible and their folders open. The last p shows when
nothing matches.
<nav class="tree" aria-label="Theme files" style="max-width: 18rem">
<input type="search" placeholder="Search" aria-label="Search theme files" />
<ul>
<li>
<details>
<summary><i class="icon icon-folder" aria-hidden="true"></i><span>assets</span></summary>
<ul>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>js/cart.js</span></a></li>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>img/hero.jpg</span></a></li>
</ul>
</details>
</li>
<li>
<details>
<summary><i class="icon icon-folder" aria-hidden="true"></i><span>layouts</span></summary>
<ul>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>base.html</span></a></li>
</ul>
</details>
</li>
<li>
<details open>
<summary><i class="icon icon-folder" aria-hidden="true"></i><span>templates</span></summary>
<ul>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>cart.html</span></a></li>
<li><a href="#" aria-current="page"><i class="icon icon-file" aria-hidden="true"></i><span>index.html</span></a></li>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>product.html</span></a></li>
</ul>
</details>
</li>
</ul>
<p hidden>No files found.</p>
</nav>
Folders can nest, links can sit outside any folder, and icons are optional
<nav class="tree" aria-label="Help articles" style="max-width: 18rem">
<ul>
<li>
<details open>
<summary><span>Orders</span></summary>
<ul>
<li><a href="#"><span>Refund an order</span></a></li>
<li>
<details>
<summary><span>Fulfillment</span></summary>
<ul>
<li><a href="#"><span>Split a shipment</span></a></li>
<li><a href="#"><span>Print a packing slip</span></a></li>
</ul>
</details>
</li>
</ul>
</details>
</li>
<li><a href="#"><span>Contact support</span></a></li>
</ul>
</nav>
aria-current marks the open file, and open on its details shows it
<nav class="tree" aria-label="Templates" style="max-width: 18rem">
<ul>
<li>
<details open>
<summary><i class="icon icon-folder" aria-hidden="true"></i><span>templates</span></summary>
<ul>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>cart.html</span></a></li>
<li><a href="#" aria-current="page"><i class="icon icon-file" aria-hidden="true"></i><span>index.html</span></a></li>
</ul>
</details>
</li>
</ul>
</nav>
A script filters the files with filter()
An empty query shows every file and restores each folder's open state.
<div class="stack" style="max-width: 18rem">
<nav class="tree" id="tr-filter" aria-label="Partials">
<ul>
<li>
<details>
<summary><i class="icon icon-folder" aria-hidden="true"></i><span>partials</span></summary>
<ul>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>footer.html</span></a></li>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>header.html</span></a></li>
<li><a href="#"><i class="icon icon-file" aria-hidden="true"></i><span>product_card.html</span></a></li>
</ul>
</details>
</li>
</ul>
<p hidden>No files found.</p>
</nav>
<div class="stack" data-direction="row" data-gap="sm">
<button type="button" class="btn" data-size="sm" id="tr-filter-er">Files with "er"</button>
<button type="button" class="btn" data-size="sm" id="tr-filter-all">Every file</button>
</div>
</div>
<script type="module">
const tree = document.getElementById("tr-filter");
document.getElementById("tr-filter-er").addEventListener("click", () => tsunami.tree.filter(tree, "er"));
document.getElementById("tr-filter-all").addEventListener("click", () => tsunami.tree.filter(tree, ""));
</script>
API
Lives in js/tree.js. A tree without a search field
needs no JS. It fires no events of its own;
details fires the browser's toggle.
| Function | Arguments | Description |
|---|---|---|
tsunami.tree.filter(tree, query) |
tree: element .tree,
query: string
|
Shows only files whose name contains query
(case-insensitive). Hides folders with no match and opens
folders with one. An empty query shows every file and
restores the folders.
|
input[type="search"] |
in .tree |
Calls filter() as you type |
Tab / Enter / Space |
keyboard | Native browser behaviour: move between folders and files, open and close folders, follow links |
Attributes
| Attribute / element | Values | Description |
|---|---|---|
.tree |
nav |
Add an aria-label (ul.tree also
works)
|
ul > li |
child |
One item each. A ul nested in an
li is indented on a guide line.
|
details > summary |
in li |
Folder. The chevron turns when open. Followed by a
ul of files.
|
open |
on details |
Folder is open (native) |
a / span |
in li |
File (link) or a non-link item. The label in its
span truncates with … when long.
|
.icon |
in summary / a |
Optional, such as icon-folder,
icon-file
|
aria-current |
page |
On a link: the open file |
input[type="search"] |
direct child of .tree |
Search field, filters as you type |
p |
direct child of .tree |
Message when no file matches. Start it
hidden; JS shows and hides it.
|
hidden |
on li |
Set by JS on files and folders that do not match |