Open demo

CSS code

HTML
<nav class="tree" aria-labelledby="tree-h">
  <p class="tree-h" id="tree-h">Explorer</p>
  <ul>
    <li>
      <details open>
        <summary><span aria-hidden="true">πŸ“</span> src</summary>
        <ul>
          <li>
            <details open>
              <summary><span aria-hidden="true">πŸ“</span> components</summary>
              <ul>
                <li><a class="tree-f" href="#" aria-current="page"><span aria-hidden="true">πŸ“„</span> Header.tsx</a></li>
                <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ“„</span> Card.tsx <span class="tree-b"><span aria-hidden="true">M</span><span class="tree-sr">, modified</span></span></a></li>
                <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ“„</span> Table.tsx</a></li>
              </ul>
            </details>
          </li>
          <li>
            <details>
              <summary><span aria-hidden="true">πŸ“</span> lib</summary>
              <ul>
                <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ“„</span> utils.ts</a></li>
              </ul>
            </details>
          </li>
          <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ“„</span> index.ts</a></li>
        </ul>
      </details>
    </li>
    <li>
      <details>
        <summary><span aria-hidden="true">πŸ“</span> public</summary>
        <ul>
          <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ–ΌοΈ</span> logo.svg</a></li>
        </ul>
      </details>
    </li>
    <li><a class="tree-f" href="#"><span aria-hidden="true">πŸ“„</span> package.json <span class="tree-b"><span aria-hidden="true">M</span><span class="tree-sr">, modified</span></span></a></li>
  </ul>
</nav>
CSS
/* File tree navigation: nested lists, each folder a native <details> disclosure, the open file marked with aria-current. */
.tree-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.tree{width:100%;max-width:360px;background:#171b24;border:1px solid #252c3a;border-radius:14px;padding:18px 14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86rem}
.tree ul{list-style:none;margin:0;padding:0}
.tree ul ul{padding-left:14px}
.tree-h{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#8793ab;padding:0 10px 10px}
.tree summary{list-style:none;display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:8px;cursor:pointer;color:#c9d2e0}
.tree summary::-webkit-details-marker{display:none}
.tree summary:hover{background:#1f2531}
.tree summary::before{content:"β–Έ";font-size:.7rem;color:#8793ab;transition:transform .15s}
.tree details[open]>summary::before{transform:rotate(90deg)}
.tree-f{display:flex;align-items:center;gap:8px;padding:6px 10px 6px 32px;border-radius:8px;color:#8d99b0;text-decoration:none}
.tree ul ul .tree-f{padding-left:18px}
.tree-f:hover{background:#1f2531;color:#e6ecf7}
.tree-f[aria-current="page"]{background:#20304a;color:#7cc6ff}
.tree-b{margin-left:auto;font-size:.66rem;background:#252c3a;border-radius:5px;padding:1px 7px;color:#aab4c7}
.tree a:focus-visible,.tree summary:focus-visible{outline:2px solid #7cc6ff;outline-offset:-2px}
@media (prefers-reduced-motion:reduce){.tree summary::before{transition:none}}
JavaScript
/* Optional: Esc inside an open folder closes that folder and returns focus to its summary. Folders open and close without script. */
document.querySelectorAll('.tree details').forEach(d => d.addEventListener('keydown', e => {
  if (e.key === 'Escape' && d.open) { e.stopPropagation(); d.open = false; d.querySelector('summary').focus(); }
}));

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<nav class="tree" aria-labelledby="tree-h">
  <p class="tree-h" id="tree-h">Explorer</p>
  <ul class="list-unstyled m-0">
    <li>
      <button class="tree-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#tree-src" aria-expanded="true" aria-controls="tree-src"><span aria-hidden="true">πŸ“</span> src</button>
      <ul class="list-unstyled m-0 collapse show" id="tree-src">
        <li>
          <button class="tree-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#tree-components" aria-expanded="true" aria-controls="tree-components"><span aria-hidden="true">πŸ“</span> components</button>
          <ul class="list-unstyled m-0 collapse show" id="tree-components">
            <li><a class="nav-link active" href="#" aria-current="page"><span aria-hidden="true">πŸ“„</span> Header.tsx</a></li>
            <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ“„</span> Card.tsx <span class="badge"><span aria-hidden="true">M</span><span class="visually-hidden">, modified</span></span></a></li>
            <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ“„</span> Table.tsx</a></li>
          </ul>
        </li>
        <li>
          <button class="tree-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#tree-lib" aria-expanded="false" aria-controls="tree-lib"><span aria-hidden="true">πŸ“</span> lib</button>
          <ul class="list-unstyled m-0 collapse" id="tree-lib">
            <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ“„</span> utils.ts</a></li>
          </ul>
        </li>
        <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ“„</span> index.ts</a></li>
      </ul>
    </li>
    <li>
      <button class="tree-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#tree-public" aria-expanded="false" aria-controls="tree-public"><span aria-hidden="true">πŸ“</span> public</button>
      <ul class="list-unstyled m-0 collapse" id="tree-public">
        <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ–ΌοΈ</span> logo.svg</a></li>
      </ul>
    </li>
    <li><a class="nav-link" href="#"><span aria-hidden="true">πŸ“„</span> package.json <span class="badge"><span aria-hidden="true">M</span><span class="visually-hidden">, modified</span></span></a></li>
  </ul>
</nav>
CSS
/* File tree built from nested lists. Each folder is a button that toggles a .collapse through the Collapse plugin,
   which keeps aria-expanded in sync; a few lines of script add Esc to close the folder you are in. */
.tree{width:100%;max-width:360px;background:#171b24;border:1px solid #252c3a;border-radius:14px;padding:18px 14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86rem}
.tree ul ul{padding-left:14px}
.tree-h{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#8793ab;padding:0 10px 10px}
.tree-toggle{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:8px;background:none;font:inherit;line-height:normal;text-align:left;color:#c9d2e0}
.tree-toggle:hover{background:#1f2531}
.tree-toggle::before{content:"β–Έ";font-size:.7rem;color:#8793ab;transition:transform .15s}
.tree-toggle[aria-expanded="true"]::before{transform:rotate(90deg)}
.tree .nav-link{--bs-nav-link-padding-x:10px;--bs-nav-link-padding-y:6px;--bs-nav-link-color:#8d99b0;--bs-nav-link-hover-color:#e6ecf7;display:flex;align-items:center;gap:8px;padding-left:32px;border-radius:8px;line-height:normal}
.tree ul ul .nav-link{padding-left:18px}
.tree .nav-link:hover{background:#1f2531}
.tree .nav-link.active{background:#20304a;color:#7cc6ff}
.tree .badge{--bs-badge-font-size:.66rem;--bs-badge-font-weight:400;--bs-badge-padding-x:7px;--bs-badge-padding-y:1px;--bs-badge-border-radius:5px;--bs-badge-color:#aab4c7;margin-left:auto;background:#252c3a;line-height:normal}
.tree .nav-link:focus-visible,.tree-toggle:focus-visible{outline:2px solid #7cc6ff;outline-offset:-2px;box-shadow:none}
@media (prefers-reduced-motion:reduce){.tree-toggle::before,.tree .collapsing{transition:none}}
JavaScript
/* Esc closes the folder you are in (or the open folder whose button has focus) and returns focus to its button. */
document.querySelectorAll('.tree').forEach(tree => tree.addEventListener('keydown', e => {
  if (e.key !== 'Escape') return;
  const t = e.target, own = t.matches('[aria-expanded="true"]') && document.querySelector(t.dataset.bsTarget);
  const list = own || t.closest('.collapse.show');
  if (!list) return;
  bootstrap.Collapse.getOrCreateInstance(list, { toggle: false }).hide();
  tree.querySelector(`[data-bs-target="#${list.id}"]`).focus();
}));

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<nav class="tree w-full max-w-[360px] rounded-[14px] border border-tree-line bg-tree-bg px-3.5 py-[18px] font-mono text-[.86rem]" aria-labelledby="tree-h">
  <p class="px-2.5 pb-2.5 font-[-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif] text-[.72rem] font-extrabold tracking-[.1em] text-tree-muted uppercase" id="tree-h">Explorer</p>
  <ul>
    <li>
      <details class="group/src" open>
        <summary class="flex cursor-pointer list-none items-center gap-2 rounded-lg px-2.5 py-1.5 before:text-[.7rem] before:text-tree-muted before:transition-transform before:duration-150 before:content-['β–Έ'] group-open/src:before:rotate-90 hover:bg-tree-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent motion-reduce:before:transition-none [&::-webkit-details-marker]:hidden"><span aria-hidden="true">πŸ“</span> src</summary>
        <ul class="pl-3.5">
          <li>
            <details class="group/components" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-lg px-2.5 py-1.5 before:text-[.7rem] before:text-tree-muted before:transition-transform before:duration-150 before:content-['β–Έ'] group-open/components:before:rotate-90 hover:bg-tree-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent motion-reduce:before:transition-none [&::-webkit-details-marker]:hidden"><span aria-hidden="true">πŸ“</span> components</summary>
              <ul class="pl-3.5">
                <li><a class="flex items-center gap-2 rounded-lg bg-[#20304a] py-1.5 pr-2.5 pl-[18px] text-tree-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#" aria-current="page"><span aria-hidden="true">πŸ“„</span> Header.tsx</a></li>
                <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-[18px] text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ“„</span> Card.tsx <span class="ml-auto rounded-[5px] bg-tree-line px-[7px] py-px text-[.66rem] text-[#aab4c7]"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></a></li>
                <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-[18px] text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ“„</span> Table.tsx</a></li>
              </ul>
            </details>
          </li>
          <li>
            <details class="group/lib">
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-lg px-2.5 py-1.5 before:text-[.7rem] before:text-tree-muted before:transition-transform before:duration-150 before:content-['β–Έ'] group-open/lib:before:rotate-90 hover:bg-tree-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent motion-reduce:before:transition-none [&::-webkit-details-marker]:hidden"><span aria-hidden="true">πŸ“</span> lib</summary>
              <ul class="pl-3.5">
                <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-[18px] text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ“„</span> utils.ts</a></li>
              </ul>
            </details>
          </li>
          <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-[18px] text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ“„</span> index.ts</a></li>
        </ul>
      </details>
    </li>
    <li>
      <details class="group/public">
        <summary class="flex cursor-pointer list-none items-center gap-2 rounded-lg px-2.5 py-1.5 before:text-[.7rem] before:text-tree-muted before:transition-transform before:duration-150 before:content-['β–Έ'] group-open/public:before:rotate-90 hover:bg-tree-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent motion-reduce:before:transition-none [&::-webkit-details-marker]:hidden"><span aria-hidden="true">πŸ“</span> public</summary>
        <ul class="pl-3.5">
          <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-[18px] text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ–ΌοΈ</span> logo.svg</a></li>
        </ul>
      </details>
    </li>
    <li><a class="flex items-center gap-2 rounded-lg py-1.5 pr-2.5 pl-8 text-tree-file hover:bg-tree-hover hover:text-[#e6ecf7] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tree-accent" href="#"><span aria-hidden="true">πŸ“„</span> package.json <span class="ml-auto rounded-[5px] bg-tree-line px-[7px] py-px text-[.66rem] text-[#aab4c7]"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></a></li>
  </ul>
</nav>
CSS
@theme {
  --color-tree-bg: #171b24;
  --color-tree-line: #252c3a;
  --color-tree-hover: #1f2531;
  --color-tree-file: #8d99b0;
  --color-tree-muted: #8793ab;
  --color-tree-accent: #7cc6ff;
}
JavaScript
/* Optional: Esc inside an open folder closes that folder and returns focus to its summary. Folders open and close without script. */
document.querySelectorAll('.tree details').forEach(d => d.addEventListener('keydown', e => {
  if (e.key === 'Escape' && d.open) { e.stopPropagation(); d.open = false; d.querySelector('summary').focus(); }
}));

About this set

A dark, monospaced file explorer like the sidebar of a code editor. Under an Explorer heading, folders (src, components, lib, public) expand and collapse with a turning chevron, files are indented by depth, the open file Header.tsx is highlighted in blue and two files carry an M badge for modified. Use it for documentation sites organised in folders, code browsers, or any nested navigation where the hierarchy matters more than the individual page. The structure is nested lists, so the depth of every item is part of the markup and screen readers can announce list levels. Each folder is a native details element, which makes it a keyboard-operable disclosure with no script; an optional handler lets Esc close the folder you are in and return focus to its name. The open file carries aria-current=”page” and the M badges include hidden text that reads as modified. The Bootstrap version turns each folder into a button that toggles a .collapse through the Collapse plugin, and the Tailwind version keeps details and summary with named group-open variants.

What’s included

  • Folders that expand and collapse with a rotating chevron
  • Depth shown by nested lists and indentation
  • Open file highlighted and marked with aria-current
  • Modified badges with screen reader text
  • Esc closes the folder you are in

Accessibility

The tree is a nav named by its Explorer heading. Folder names are disclosures announced as expanded or collapsed (summary elements, or buttons with aria-expanded in Bootstrap) and toggle with Enter or Space. Links and folder names show a 2px light blue outline on keyboard focus, the M badges read as modified, and the chevron animation is removed under prefers-reduced-motion.

Customise it

Change the panel, hover and highlight colours in .tree and .tree-f (plain CSS), the .tree .nav-link variables and .badge colours (Bootstrap) or the --color-tree-* theme colours (Tailwind). Indentation per level is the 14px padding on nested lists.