Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="ide-folder" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h8.3A1.6 1.6 0 0 1 21 8.8v9.6a1.6 1.6 0 0 1-1.6 1.6H4.6A1.6 1.6 0 0 1 3 18.4z" fill="currentColor"/></symbol>
  <symbol id="ide-folder-open" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h7.3A1.6 1.6 0 0 1 20 8.8V10H7.4a1.6 1.6 0 0 0-1.5 1.1L3 18.6z" fill="currentColor" opacity=".55"/><path d="M6.4 11.3A1.6 1.6 0 0 1 7.9 10.2h13.2a1 1 0 0 1 .96 1.3l-2.2 7.3a1.6 1.6 0 0 1-1.5 1.2H4.3a1 1 0 0 1-.96-1.3z" fill="currentColor"/></symbol>
  <symbol id="ide-code" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M10 12.5l-2.2 2.3 2.2 2.3M14 12.5l2.2 2.3-2.2 2.3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-doc" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M8.4 13h7.2M8.4 16.6h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
  <symbol id="ide-cube" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z M4.4 7.7L12 12l7.6-4.3M12 12v8.6" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-method" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z" fill="currentColor" fill-opacity=".28" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-prop" viewBox="0 0 24 24"><path d="M14.5 4.5a4.5 4.5 0 0 0-4.3 5.8L4 16.5 7.5 20l6.2-6.2a4.5 4.5 0 0 0 5.8-4.3l-2.6 2.6-2.6-.4-.4-2.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-var" viewBox="0 0 24 24"><rect x="3.5" y="7" width="17" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8 10l2 4 2-4M14 10h2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-const" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8.5 10h7M8.5 14h7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></symbol>
  <symbol id="ide-chev" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></symbol>
</svg>
<div class="grid">
<div><span class="lbl">IDE explorer with git status and indent guides</span>
<div class="ide-card">
  <div class="ide-head"><p class="ide-title">Explorer</p><span class="ide-file">orbit-api</span></div>
  <ul class="ide" role="tree" aria-label="Project files" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1">
      <div class="ide-row" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>orbit-api</span></div>
      <ul role="group" class="ide-group" style="--d:0">
        <li role="treeitem" aria-expanded="false" tabindex="-1">
          <div class="ide-row" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>.vscode</span></div>
          <ul role="group" class="ide-group" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-amber" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>settings.json</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="true" tabindex="-1">
          <div class="ide-row" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>src</span></div>
          <ul role="group" class="ide-group" style="--d:1">
            <li role="treeitem" aria-expanded="true" tabindex="-1">
              <div class="ide-row" data-row style="--d:2"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>controllers</span><span class="ide-dot" title="Contains changes"><span class="sr-only">, contains changes</span></span></div>
              <ul role="group" class="ide-group" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>users.controller.ts</span><span class="ide-git git-m"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>billing.controller.ts</span><span class="ide-err">2<span class="sr-only"> problems</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>health.controller.ts</span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-expanded="false" tabindex="-1">
              <div class="ide-row" data-row style="--d:2"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>services</span><span class="ide-dot" title="Contains changes"><span class="sr-only">, contains changes</span></span></div>
              <ul role="group" class="ide-group" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>mailer.service.ts</span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>stripe.service.ts</span><span class="ide-git git-u"><span aria-hidden="true">U</span><span class="sr-only">, untracked</span></span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-selected="true" tabindex="0"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>main.ts</span><span class="ide-git git-m"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>app.module.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="false" tabindex="-1">
          <div class="ide-row" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>test</span></div>
          <ul role="group" class="ide-group" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>users.e2e-spec.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-grey" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="ide-name" data-name>.env.example</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-pink" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>docker-compose.yml</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-amber" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>package.json</span><span class="ide-git git-m"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-grey" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="ide-name" data-name>README.md</span></div></li>
      </ul>
    </li>
  </ul>
  <p class="ide-foot"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="6" cy="5" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="6" cy="19" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="18" cy="8" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 7.2v9.6M18 10.2c0 4-6 3-11 6.5" fill="none" stroke="currentColor" stroke-width="1.8"/></svg> main <span aria-hidden="true">·</span> 3 changed <span aria-hidden="true">·</span> 2 problems</p>
</div>
</div>
<div><span class="lbl">Symbol outline</span>
<div class="ide-card">
  <div class="ide-head"><p class="ide-title">Outline</p><span class="ide-file">checkout.ts</span></div>
  <ul class="ide" role="tree" aria-label="Symbols in checkout.ts" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1">
      <div class="ide-row" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-orange" aria-hidden="true" focusable="false"><use href="#ide-cube"/></svg><span class="ide-name" data-name>CheckoutService</span><span class="ide-meta">L6</span></div>
      <ul role="group" class="ide-group" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="ide-name" data-name>cart</span><span class="ide-meta">L8</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="ide-name" data-name>taxRate</span><span class="ide-meta">L9</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>constructor</span><span class="ide-meta">L11</span></div></li>
        <li role="treeitem" aria-expanded="true" tabindex="-1">
          <div class="ide-row" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>applyCoupon</span><span class="ide-meta">L18</span></div>
          <ul role="group" class="ide-group" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>code</span><span class="ide-meta">L19</span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>discount</span><span class="ide-meta">L22</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="true" tabindex="0"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>placeOrder</span><span class="ide-meta">L31</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>refund</span><span class="ide-meta">L54</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-expanded="false" tabindex="-1">
      <div class="ide-row" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>formatPrice</span><span class="ide-meta">L70</span></div>
      <ul role="group" class="ide-group" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>locale</span><span class="ide-meta">L71</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row" data-row style="--d:0"><span class="ide-sp"></span><svg class="ide-ico c-cyan" aria-hidden="true" focusable="false"><use href="#ide-const"/></svg><span class="ide-name" data-name>CURRENCY</span><span class="ide-meta">L3</span></div></li>
  </ul>
  <p class="ide-foot">9 symbols <span aria-hidden="true">·</span> TypeScript</p>
</div>
</div>
</div>
CSS
/* Dark IDE tree (WAI-ARIA tree pattern). Rows indent with --d; every group draws an indent guide, and the
   guide of the branch holding the selected file is brighter (:has()). Git state colours the file name. */
.ide{--ide-text:#d4d7dd;--ide-muted:#a5abb6;--ide-hover:#2a2d34;--ide-sel:#0b3a5e;--ide-focus:#3794ff;--ide-guide:#363940;--ide-guide-on:#6b717d;--ide-indent:14px}
.ide [role="treeitem"]{outline:none}
.ide [aria-expanded="false"]>[role="group"]{display:none}
.ide-group{position:relative}
.ide-group::before{content:"";position:absolute;top:0;bottom:0;left:calc(var(--d) * var(--ide-indent) + 14px);border-left:1px solid var(--ide-guide);pointer-events:none}
.ide-group:has(>[aria-selected="true"])::before{border-color:var(--ide-guide-on)}
.ide [aria-selected="true"]>.ide-row{background:var(--ide-sel);color:#fff}
.ide [role="treeitem"]:focus-visible>.ide-row{outline:1px solid var(--ide-focus);outline-offset:-1px}
.ide-row:has(.git-m){color:#e2c08d}
.ide-row:has(.git-u){color:#73c991}
.ide-row:has(.ide-err){color:#f48771}
.ide-row{height:26px;padding:0 10px 0 calc(var(--d,0) * var(--ide-indent) + 8px);cursor:pointer;user-select:none;white-space:nowrap}
.ide-row:hover{background:var(--ide-hover)}
.ide-chev{width:14px;height:14px;flex:none;color:var(--ide-muted);transition:transform .15s ease}
.ide [aria-expanded="true"]>.ide-row .ide-chev{transform:rotate(90deg)}
.ide-ico{width:16px;height:16px;flex:none}
.ide .ide-open,.ide [aria-expanded="true"]>.ide-row .ide-closed{display:none}
.ide [aria-expanded="true"]>.ide-row .ide-open{display:block}
.ide-sp{width:14px;flex:none}
.ide-name{overflow:hidden;text-overflow:ellipsis}
.ide-meta{padding-left:12px;font-size:.74rem;color:#9aa1ad;font-variant-numeric:tabular-nums}
.ide-git{width:12px;text-align:center;font-size:.75rem;font-weight:700}
.ide-err{padding:0 6px;border-radius:99px;background:#5a1d1d;color:#ffb4a8;font-size:.7rem;font-weight:700;line-height:1.5}
.ide-dot{width:6px;height:6px;margin-right:4px;border-radius:50%;background:#e2c08d}
.c-folder{color:#c09553}.c-blue{color:#4fa3f7}.c-amber{color:#e5c07b}.c-pink{color:#e06c9f}.c-grey{color:#a5abb6}.c-purple{color:#c49bf0}.c-orange{color:#ee9d28}.c-cyan{color:#56c8d8}
@media (prefers-reduced-motion:reduce){.ide-chev{transition:none}}
.ide,.ide ul{margin:0;padding:0;list-style:none}
.ide{padding:6px 0;font-family:system-ui,sans-serif;font-size:.84rem;color:var(--ide-text)}
.ide-row{display:flex;align-items:center;gap:6px}
.ide-meta,.ide-git,.ide-err,.ide-dot{margin-left:auto}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.ide-card{background:#1e1f24;border:1px solid #2b2d33;border-radius:10px;overflow:hidden;box-shadow:0 24px 50px -28px rgba(0,0,0,.9)}
.ide-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#18191d;border-bottom:1px solid #2b2d33}
.ide-title{margin:0;font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ide-muted)}
.ide-file{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.75rem;color:var(--ide-muted)}
.ide-foot{display:flex;align-items:center;gap:8px;margin:0;padding:8px 14px;background:#18191d;border-top:1px solid #2b2d33;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.74rem;color:var(--ide-muted)}
JavaScript
/* WAI-ARIA tree view: one tab stop (roving tabindex), Up/Down move, Right opens or goes to the first child,
   Left closes or goes to the parent, Home/End, type-ahead, Enter/Space open a folder or select a file. */
document.querySelectorAll('[role="tree"][data-tree]').forEach(tree => {
  const all = () => [...tree.querySelectorAll('[role="treeitem"]')];
  const shown = () => all().filter(i => !i.parentElement.closest('[role="treeitem"][aria-expanded="false"]'));
  const isFolder = i => i.hasAttribute('aria-expanded');
  const parentOf = i => i.parentElement.closest('[role="treeitem"]');
  const nameOf = i => i.querySelector(':scope > [data-row] [data-name]').textContent.trim();
  const out = document.getElementById(tree.dataset.status || '');
  let typed = '', timer;
  const focus = i => { all().forEach(n => { n.tabIndex = -1; }); i.tabIndex = 0; i.focus(); };
  const toggle = (i, open = i.getAttribute('aria-expanded') !== 'true') => i.setAttribute('aria-expanded', String(open));
  const select = i => {
    all().forEach(n => { if (!isFolder(n)) n.setAttribute('aria-selected', 'false'); });
    i.setAttribute('aria-selected', 'true');
    if (out) { const p = []; for (let n = i; n; n = parentOf(n)) p.unshift(nameOf(n)); out.textContent = p.join('/'); }
  };
  tree.addEventListener('click', e => {
    const row = e.target.closest('[data-row]'); if (!row) return;
    const i = row.parentElement; focus(i); isFolder(i) ? toggle(i) : select(i);
  });
  tree.addEventListener('keydown', e => {
    const i = e.target.closest('[role="treeitem"]'); if (!i || e.altKey || e.ctrlKey || e.metaKey) return;
    const list = shown(), n = list.indexOf(i), open = i.getAttribute('aria-expanded') === 'true'; let to = null;
    switch (e.key) {
      case 'ArrowDown': to = list[n + 1]; break;
      case 'ArrowUp': to = list[n - 1]; break;
      case 'Home': to = list[0]; break;
      case 'End': to = list[list.length - 1]; break;
      case 'ArrowRight': if (isFolder(i)) open ? to = i.querySelector('[role="treeitem"]') : toggle(i, true); break;
      case 'ArrowLeft': if (isFolder(i) && open) toggle(i, false); else to = parentOf(i); break;
      case 'Enter': case ' ': isFolder(i) ? toggle(i) : select(i); break;
      case '*': [...i.parentElement.children].filter(isFolder).forEach(s => toggle(s, true)); break;
      default: {
        if (e.key.length !== 1 || !/\S/.test(e.key)) return;
        typed += e.key.toLowerCase(); clearTimeout(timer); timer = setTimeout(() => { typed = ''; }, 500);
        const start = typed.length > 1 ? n : n + 1;
        to = [...list.slice(start), ...list.slice(0, start)].find(x => nameOf(x).toLowerCase().startsWith(typed));
      }
    }
    e.preventDefault(); if (to) focus(to);
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="ide-folder" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h8.3A1.6 1.6 0 0 1 21 8.8v9.6a1.6 1.6 0 0 1-1.6 1.6H4.6A1.6 1.6 0 0 1 3 18.4z" fill="currentColor"/></symbol>
  <symbol id="ide-folder-open" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h7.3A1.6 1.6 0 0 1 20 8.8V10H7.4a1.6 1.6 0 0 0-1.5 1.1L3 18.6z" fill="currentColor" opacity=".55"/><path d="M6.4 11.3A1.6 1.6 0 0 1 7.9 10.2h13.2a1 1 0 0 1 .96 1.3l-2.2 7.3a1.6 1.6 0 0 1-1.5 1.2H4.3a1 1 0 0 1-.96-1.3z" fill="currentColor"/></symbol>
  <symbol id="ide-code" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M10 12.5l-2.2 2.3 2.2 2.3M14 12.5l2.2 2.3-2.2 2.3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-doc" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M8.4 13h7.2M8.4 16.6h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
  <symbol id="ide-cube" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z M4.4 7.7L12 12l7.6-4.3M12 12v8.6" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-method" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z" fill="currentColor" fill-opacity=".28" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-prop" viewBox="0 0 24 24"><path d="M14.5 4.5a4.5 4.5 0 0 0-4.3 5.8L4 16.5 7.5 20l6.2-6.2a4.5 4.5 0 0 0 5.8-4.3l-2.6 2.6-2.6-.4-.4-2.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-var" viewBox="0 0 24 24"><rect x="3.5" y="7" width="17" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8 10l2 4 2-4M14 10h2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-const" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8.5 10h7M8.5 14h7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></symbol>
  <symbol id="ide-chev" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></symbol>
</svg>
<div class="grid">
<div><span class="lbl">IDE explorer with git status and indent guides</span>
<div class="card ide-card rounded-3 overflow-hidden">
  <div class="card-header ide-head d-flex align-items-center justify-content-between"><p class="ide-title fw-bold text-uppercase mb-0">Explorer</p><span class="ide-file font-monospace">orbit-api</span></div>
  <ul class="ide list-unstyled mb-0" role="tree" aria-label="Project files" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1">
      <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>orbit-api</span></div>
      <ul role="group" class="ide-group list-unstyled mb-0" style="--d:0">
        <li role="treeitem" aria-expanded="false" tabindex="-1">
          <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>.vscode</span></div>
          <ul role="group" class="ide-group list-unstyled mb-0" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-amber" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>settings.json</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="true" tabindex="-1">
          <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>src</span></div>
          <ul role="group" class="ide-group list-unstyled mb-0" style="--d:1">
            <li role="treeitem" aria-expanded="true" tabindex="-1">
              <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>controllers</span><span class="ide-dot ms-auto rounded-circle" title="Contains changes"><span class="visually-hidden">, contains changes</span></span></div>
              <ul role="group" class="ide-group list-unstyled mb-0" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>users.controller.ts</span><span class="ide-git git-m ms-auto"><span aria-hidden="true">M</span><span class="visually-hidden">, modified</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>billing.controller.ts</span><span class="ide-err badge rounded-pill ms-auto">2<span class="visually-hidden"> problems</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>health.controller.ts</span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-expanded="false" tabindex="-1">
              <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>services</span><span class="ide-dot ms-auto rounded-circle" title="Contains changes"><span class="visually-hidden">, contains changes</span></span></div>
              <ul role="group" class="ide-group list-unstyled mb-0" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>mailer.service.ts</span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:3"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>stripe.service.ts</span><span class="ide-git git-u ms-auto"><span aria-hidden="true">U</span><span class="visually-hidden">, untracked</span></span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-selected="true" tabindex="0"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>main.ts</span><span class="ide-git git-m ms-auto"><span aria-hidden="true">M</span><span class="visually-hidden">, modified</span></span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>app.module.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="false" tabindex="-1">
          <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico ide-closed c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="ide-ico ide-open c-folder" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="ide-name" data-name>test</span></div>
          <ul role="group" class="ide-group list-unstyled mb-0" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>users.e2e-spec.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-grey" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="ide-name" data-name>.env.example</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-pink" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>docker-compose.yml</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-amber" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="ide-name" data-name>package.json</span><span class="ide-git git-m ms-auto"><span aria-hidden="true">M</span><span class="visually-hidden">, modified</span></span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-grey" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="ide-name" data-name>README.md</span></div></li>
      </ul>
    </li>
  </ul>
  <p class="card-footer ide-foot d-flex align-items-center gap-2 mb-0"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="6" cy="5" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="6" cy="19" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="18" cy="8" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 7.2v9.6M18 10.2c0 4-6 3-11 6.5" fill="none" stroke="currentColor" stroke-width="1.8"/></svg> main <span aria-hidden="true">·</span> 3 changed <span aria-hidden="true">·</span> 2 problems</p>
</div>
</div>
<div><span class="lbl">Symbol outline</span>
<div class="card ide-card rounded-3 overflow-hidden">
  <div class="card-header ide-head d-flex align-items-center justify-content-between"><p class="ide-title fw-bold text-uppercase mb-0">Outline</p><span class="ide-file font-monospace">checkout.ts</span></div>
  <ul class="ide list-unstyled mb-0" role="tree" aria-label="Symbols in checkout.ts" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1">
      <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-orange" aria-hidden="true" focusable="false"><use href="#ide-cube"/></svg><span class="ide-name" data-name>CheckoutService</span><span class="ide-meta ms-auto">L6</span></div>
      <ul role="group" class="ide-group list-unstyled mb-0" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="ide-name" data-name>cart</span><span class="ide-meta ms-auto">L8</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="ide-name" data-name>taxRate</span><span class="ide-meta ms-auto">L9</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>constructor</span><span class="ide-meta ms-auto">L11</span></div></li>
        <li role="treeitem" aria-expanded="true" tabindex="-1">
          <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>applyCoupon</span><span class="ide-meta ms-auto">L18</span></div>
          <ul role="group" class="ide-group list-unstyled mb-0" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>code</span><span class="ide-meta ms-auto">L19</span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:2"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>discount</span><span class="ide-meta ms-auto">L22</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="true" tabindex="0"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>placeOrder</span><span class="ide-meta ms-auto">L31</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>refund</span><span class="ide-meta ms-auto">L54</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-expanded="false" tabindex="-1">
      <div class="ide-row d-flex align-items-center gap-1" data-row style="--d:0"><svg class="ide-chev" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="ide-ico c-purple" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="ide-name" data-name>formatPrice</span><span class="ide-meta ms-auto">L70</span></div>
      <ul role="group" class="ide-group list-unstyled mb-0" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:1"><span class="ide-sp"></span><svg class="ide-ico c-blue" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="ide-name" data-name>locale</span><span class="ide-meta ms-auto">L71</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-selected="false" tabindex="-1"><div class="ide-row d-flex align-items-center gap-1" data-row style="--d:0"><span class="ide-sp"></span><svg class="ide-ico c-cyan" aria-hidden="true" focusable="false"><use href="#ide-const"/></svg><span class="ide-name" data-name>CURRENCY</span><span class="ide-meta ms-auto">L3</span></div></li>
  </ul>
  <p class="card-footer ide-foot d-flex align-items-center gap-2 mb-0">9 symbols <span aria-hidden="true">·</span> TypeScript</p>
</div>
</div>
</div>
CSS
/* Dark IDE tree (WAI-ARIA tree pattern). Rows indent with --d; every group draws an indent guide, and the
   guide of the branch holding the selected file is brighter (:has()). Git state colours the file name. */
.ide{--ide-text:#d4d7dd;--ide-muted:#a5abb6;--ide-hover:#2a2d34;--ide-sel:#0b3a5e;--ide-focus:#3794ff;--ide-guide:#363940;--ide-guide-on:#6b717d;--ide-indent:14px}
.ide [role="treeitem"]{outline:none}
.ide [aria-expanded="false"]>[role="group"]{display:none}
.ide-group{position:relative}
.ide-group::before{content:"";position:absolute;top:0;bottom:0;left:calc(var(--d) * var(--ide-indent) + 14px);border-left:1px solid var(--ide-guide);pointer-events:none}
.ide-group:has(>[aria-selected="true"])::before{border-color:var(--ide-guide-on)}
.ide [aria-selected="true"]>.ide-row{background:var(--ide-sel);color:#fff}
.ide [role="treeitem"]:focus-visible>.ide-row{outline:1px solid var(--ide-focus);outline-offset:-1px}
.ide-row:has(.git-m){color:#e2c08d}
.ide-row:has(.git-u){color:#73c991}
.ide-row:has(.ide-err){color:#f48771}
.ide-row{height:26px;padding:0 10px 0 calc(var(--d,0) * var(--ide-indent) + 8px);cursor:pointer;user-select:none;white-space:nowrap}
.ide-row:hover{background:var(--ide-hover)}
.ide-chev{width:14px;height:14px;flex:none;color:var(--ide-muted);transition:transform .15s ease}
.ide [aria-expanded="true"]>.ide-row .ide-chev{transform:rotate(90deg)}
.ide-ico{width:16px;height:16px;flex:none}
.ide .ide-open,.ide [aria-expanded="true"]>.ide-row .ide-closed{display:none}
.ide [aria-expanded="true"]>.ide-row .ide-open{display:block}
.ide-sp{width:14px;flex:none}
.ide-name{overflow:hidden;text-overflow:ellipsis}
.ide-meta{padding-left:12px;font-size:.74rem;color:#9aa1ad;font-variant-numeric:tabular-nums}
.ide-git{width:12px;text-align:center;font-size:.75rem;font-weight:700}
.ide-err{padding:0 6px;border-radius:99px;background:#5a1d1d;color:#ffb4a8;font-size:.7rem;font-weight:700;line-height:1.5}
.ide-dot{width:6px;height:6px;margin-right:4px;border-radius:50%;background:#e2c08d}
.c-folder{color:#c09553}.c-blue{color:#4fa3f7}.c-amber{color:#e5c07b}.c-pink{color:#e06c9f}.c-grey{color:#a5abb6}.c-purple{color:#c49bf0}.c-orange{color:#ee9d28}.c-cyan{color:#56c8d8}
@media (prefers-reduced-motion:reduce){.ide-chev{transition:none}}
.ide{padding:6px 0;font-family:system-ui,sans-serif;font-size:.84rem;color:var(--ide-text)}
.ide .ide-row{gap:6px!important}
.ide-err{--bs-badge-padding-x:6px;--bs-badge-padding-y:0;--bs-badge-font-size:.7rem}
.ide-card{--bs-card-bg:#1e1f24;--bs-card-border-color:#2b2d33;--bs-card-cap-bg:#18191d;--bs-card-cap-padding-x:14px;box-shadow:0 24px 50px -28px rgba(0,0,0,.9)}
.ide-head{--bs-card-cap-padding-y:10px}
.ide-title{font-size:.7rem;letter-spacing:.1em;color:var(--ide-muted)}
.ide-file{font-size:.75rem;color:#a5abb6}
.ide-foot{--bs-card-cap-padding-y:8px;font-family:var(--bs-font-monospace);font-size:.74rem;color:#a5abb6}
JavaScript
/* WAI-ARIA tree view: one tab stop (roving tabindex), Up/Down move, Right opens or goes to the first child,
   Left closes or goes to the parent, Home/End, type-ahead, Enter/Space open a folder or select a file. */
document.querySelectorAll('[role="tree"][data-tree]').forEach(tree => {
  const all = () => [...tree.querySelectorAll('[role="treeitem"]')];
  const shown = () => all().filter(i => !i.parentElement.closest('[role="treeitem"][aria-expanded="false"]'));
  const isFolder = i => i.hasAttribute('aria-expanded');
  const parentOf = i => i.parentElement.closest('[role="treeitem"]');
  const nameOf = i => i.querySelector(':scope > [data-row] [data-name]').textContent.trim();
  const out = document.getElementById(tree.dataset.status || '');
  let typed = '', timer;
  const focus = i => { all().forEach(n => { n.tabIndex = -1; }); i.tabIndex = 0; i.focus(); };
  const toggle = (i, open = i.getAttribute('aria-expanded') !== 'true') => i.setAttribute('aria-expanded', String(open));
  const select = i => {
    all().forEach(n => { if (!isFolder(n)) n.setAttribute('aria-selected', 'false'); });
    i.setAttribute('aria-selected', 'true');
    if (out) { const p = []; for (let n = i; n; n = parentOf(n)) p.unshift(nameOf(n)); out.textContent = p.join('/'); }
  };
  tree.addEventListener('click', e => {
    const row = e.target.closest('[data-row]'); if (!row) return;
    const i = row.parentElement; focus(i); isFolder(i) ? toggle(i) : select(i);
  });
  tree.addEventListener('keydown', e => {
    const i = e.target.closest('[role="treeitem"]'); if (!i || e.altKey || e.ctrlKey || e.metaKey) return;
    const list = shown(), n = list.indexOf(i), open = i.getAttribute('aria-expanded') === 'true'; let to = null;
    switch (e.key) {
      case 'ArrowDown': to = list[n + 1]; break;
      case 'ArrowUp': to = list[n - 1]; break;
      case 'Home': to = list[0]; break;
      case 'End': to = list[list.length - 1]; break;
      case 'ArrowRight': if (isFolder(i)) open ? to = i.querySelector('[role="treeitem"]') : toggle(i, true); break;
      case 'ArrowLeft': if (isFolder(i) && open) toggle(i, false); else to = parentOf(i); break;
      case 'Enter': case ' ': isFolder(i) ? toggle(i) : select(i); break;
      case '*': [...i.parentElement.children].filter(isFolder).forEach(s => toggle(s, true)); break;
      default: {
        if (e.key.length !== 1 || !/\S/.test(e.key)) return;
        typed += e.key.toLowerCase(); clearTimeout(timer); timer = setTimeout(() => { typed = ''; }, 500);
        const start = typed.length > 1 ? n : n + 1;
        to = [...list.slice(start), ...list.slice(0, start)].find(x => nameOf(x).toLowerCase().startsWith(typed));
      }
    }
    e.preventDefault(); if (to) focus(to);
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="ide-folder" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h8.3A1.6 1.6 0 0 1 21 8.8v9.6a1.6 1.6 0 0 1-1.6 1.6H4.6A1.6 1.6 0 0 1 3 18.4z" fill="currentColor"/></symbol>
  <symbol id="ide-folder-open" viewBox="0 0 24 24"><path d="M3 6.6A1.6 1.6 0 0 1 4.6 5h4.5l2 2.2h7.3A1.6 1.6 0 0 1 20 8.8V10H7.4a1.6 1.6 0 0 0-1.5 1.1L3 18.6z" fill="currentColor" opacity=".55"/><path d="M6.4 11.3A1.6 1.6 0 0 1 7.9 10.2h13.2a1 1 0 0 1 .96 1.3l-2.2 7.3a1.6 1.6 0 0 1-1.5 1.2H4.3a1 1 0 0 1-.96-1.3z" fill="currentColor"/></symbol>
  <symbol id="ide-code" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M10 12.5l-2.2 2.3 2.2 2.3M14 12.5l2.2 2.3-2.2 2.3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-doc" viewBox="0 0 24 24"><path d="M6.2 2.8h7.6l5 5v12.1a1.3 1.3 0 0 1-1.3 1.3H6.2a1.3 1.3 0 0 1-1.3-1.3V4.1a1.3 1.3 0 0 1 1.3-1.3z" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M13.6 3v5h5M8.4 13h7.2M8.4 16.6h5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
  <symbol id="ide-cube" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z M4.4 7.7L12 12l7.6-4.3M12 12v8.6" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-method" viewBox="0 0 24 24"><path d="M12 3.2l7.8 4.4v8.8L12 20.8l-7.8-4.4V7.6z" fill="currentColor" fill-opacity=".28" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-prop" viewBox="0 0 24 24"><path d="M14.5 4.5a4.5 4.5 0 0 0-4.3 5.8L4 16.5 7.5 20l6.2-6.2a4.5 4.5 0 0 0 5.8-4.3l-2.6 2.6-2.6-.4-.4-2.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></symbol>
  <symbol id="ide-var" viewBox="0 0 24 24"><rect x="3.5" y="7" width="17" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8 10l2 4 2-4M14 10h2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
  <symbol id="ide-const" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M8.5 10h7M8.5 14h7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></symbol>
  <symbol id="ide-chev" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></symbol>
</svg>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,300px),1fr))] items-start gap-7">
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#a5abb6] uppercase">IDE explorer with git status and indent guides</span>
<div class="overflow-hidden rounded-[10px] border border-[#2b2d33] bg-[#1e1f24] shadow-[0_24px_50px_-28px_rgba(0,0,0,.9)]">
  <div class="flex items-center justify-between border-b border-[#2b2d33] bg-[#18191d] px-3.5 py-2.5"><p class="m-0 text-[.7rem] font-bold tracking-[.1em] text-[#a5abb6] uppercase">Explorer</p><span class="font-mono text-[.75rem] text-[#a5abb6]">orbit-api</span></div>
  <ul class="ide m-0 list-none py-1.5 font-[system-ui,sans-serif] text-[.84rem] text-[#d4d7dd]" role="tree" aria-label="Project files" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1" class="outline-none">
      <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:0"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>orbit-api</span></div>
      <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:0">
        <li role="treeitem" aria-expanded="false" tabindex="-1" class="outline-none">
          <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>.vscode</span></div>
          <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#e5c07b]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>settings.json</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="true" tabindex="-1" class="outline-none">
          <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>src</span></div>
          <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:1">
            <li role="treeitem" aria-expanded="true" tabindex="-1" class="outline-none">
              <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>controllers</span><span class="ml-auto mr-1 size-1.5 rounded-full bg-[#e2c08d]" title="Contains changes"><span class="sr-only">, contains changes</span></span></div>
              <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:3"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>users.controller.ts</span><span class="git-m ml-auto w-3 text-center text-[.75rem] font-bold text-[#e2c08d]"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:3"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>billing.controller.ts</span><span class="git-e ml-auto rounded-full bg-[#5a1d1d] px-1.5 text-[.7rem] font-bold leading-[1.5] text-[#ffb4a8]">2<span class="sr-only"> problems</span></span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:3"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>health.controller.ts</span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-expanded="false" tabindex="-1" class="outline-none">
              <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>services</span><span class="ml-auto mr-1 size-1.5 rounded-full bg-[#e2c08d]" title="Contains changes"><span class="sr-only">, contains changes</span></span></div>
              <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:2">
                <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:3"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>mailer.service.ts</span></div></li>
                <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:3"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>stripe.service.ts</span><span class="git-u ml-auto w-3 text-center text-[.75rem] font-bold text-[#73c991]"><span aria-hidden="true">U</span><span class="sr-only">, untracked</span></span></div></li>
              </ul>
            </li>
            <li role="treeitem" aria-selected="true" tabindex="0" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>main.ts</span><span class="git-m ml-auto w-3 text-center text-[.75rem] font-bold text-[#e2c08d]"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>app.module.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-expanded="false" tabindex="-1" class="outline-none">
          <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none [[aria-expanded=true]>div>&]:hidden text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder"/></svg><svg class="size-4 flex-none hidden [[aria-expanded=true]>div>&]:block text-[#c09553]" aria-hidden="true" focusable="false"><use href="#ide-folder-open"/></svg><span class="overflow-hidden text-ellipsis" data-name>test</span></div>
          <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>users.e2e-spec.ts</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#a5abb6]" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="overflow-hidden text-ellipsis" data-name>.env.example</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#e06c9f]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>docker-compose.yml</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#e5c07b]" aria-hidden="true" focusable="false"><use href="#ide-code"/></svg><span class="overflow-hidden text-ellipsis" data-name>package.json</span><span class="git-m ml-auto w-3 text-center text-[.75rem] font-bold text-[#e2c08d]"><span aria-hidden="true">M</span><span class="sr-only">, modified</span></span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#a5abb6]" aria-hidden="true" focusable="false"><use href="#ide-doc"/></svg><span class="overflow-hidden text-ellipsis" data-name>README.md</span></div></li>
      </ul>
    </li>
  </ul>
  <p class="flex items-center gap-2 border-t border-[#2b2d33] bg-[#18191d] px-3.5 py-2 font-mono text-[.74rem] text-[#a5abb6]"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="6" cy="5" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="6" cy="19" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="18" cy="8" r="2.2" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 7.2v9.6M18 10.2c0 4-6 3-11 6.5" fill="none" stroke="currentColor" stroke-width="1.8"/></svg> main <span aria-hidden="true">·</span> 3 changed <span aria-hidden="true">·</span> 2 problems</p>
</div>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#a5abb6] uppercase">Symbol outline</span>
<div class="overflow-hidden rounded-[10px] border border-[#2b2d33] bg-[#1e1f24] shadow-[0_24px_50px_-28px_rgba(0,0,0,.9)]">
  <div class="flex items-center justify-between border-b border-[#2b2d33] bg-[#18191d] px-3.5 py-2.5"><p class="m-0 text-[.7rem] font-bold tracking-[.1em] text-[#a5abb6] uppercase">Outline</p><span class="font-mono text-[.75rem] text-[#a5abb6]">checkout.ts</span></div>
  <ul class="ide m-0 list-none py-1.5 font-[system-ui,sans-serif] text-[.84rem] text-[#d4d7dd]" role="tree" aria-label="Symbols in checkout.ts" data-tree>
    <li role="treeitem" aria-expanded="true" tabindex="-1" class="outline-none">
      <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:0"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none text-[#ee9d28]" aria-hidden="true" focusable="false"><use href="#ide-cube"/></svg><span class="overflow-hidden text-ellipsis" data-name>CheckoutService</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L6</span></div>
      <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="overflow-hidden text-ellipsis" data-name>cart</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L8</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-prop"/></svg><span class="overflow-hidden text-ellipsis" data-name>taxRate</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L9</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#c49bf0]" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="overflow-hidden text-ellipsis" data-name>constructor</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L11</span></div></li>
        <li role="treeitem" aria-expanded="true" tabindex="-1" class="outline-none">
          <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none text-[#c49bf0]" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="overflow-hidden text-ellipsis" data-name>applyCoupon</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L18</span></div>
          <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:1">
            <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="overflow-hidden text-ellipsis" data-name>code</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L19</span></div></li>
            <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:2"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="overflow-hidden text-ellipsis" data-name>discount</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L22</span></div></li>
          </ul>
        </li>
        <li role="treeitem" aria-selected="true" tabindex="0" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#c49bf0]" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="overflow-hidden text-ellipsis" data-name>placeOrder</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L31</span></div></li>
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#c49bf0]" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="overflow-hidden text-ellipsis" data-name>refund</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L54</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-expanded="false" tabindex="-1" class="outline-none">
      <div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:0"><svg class="size-3.5 flex-none text-[#a5abb6] transition-transform duration-150 motion-reduce:transition-none [[aria-expanded=true]>div>&]:rotate-90" aria-hidden="true" focusable="false"><use href="#ide-chev"/></svg><svg class="size-4 flex-none text-[#c49bf0]" aria-hidden="true" focusable="false"><use href="#ide-method"/></svg><span class="overflow-hidden text-ellipsis" data-name>formatPrice</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L70</span></div>
      <ul role="group" class="ide-group [[aria-expanded=false]>&]:hidden" style="--d:0">
        <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:1"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#4fa3f7]" aria-hidden="true" focusable="false"><use href="#ide-var"/></svg><span class="overflow-hidden text-ellipsis" data-name>locale</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L71</span></div></li>
      </ul>
    </li>
    <li role="treeitem" aria-selected="false" tabindex="-1" class="outline-none"><div class="flex h-[26px] cursor-pointer items-center gap-1.5 pr-2.5 pl-[calc(var(--d)*14px+8px)] whitespace-nowrap select-none text-[#d4d7dd] hover:bg-[#2a2d34] has-[.git-m]:text-[#e2c08d] has-[.git-u]:text-[#73c991] has-[.git-e]:text-[#f48771] [[aria-selected=true]>&]:bg-[#0b3a5e] [[aria-selected=true]>&]:text-white [:focus-visible>&]:outline-1 [:focus-visible>&]:-outline-offset-1 [:focus-visible>&]:outline-[#3794ff]" data-row style="--d:0"><span class="w-3.5 flex-none"></span><svg class="size-4 flex-none text-[#56c8d8]" aria-hidden="true" focusable="false"><use href="#ide-const"/></svg><span class="overflow-hidden text-ellipsis" data-name>CURRENCY</span><span class="ml-auto pl-3 text-[.74rem] text-[#9aa1ad] tabular-nums">L3</span></div></li>
  </ul>
  <p class="flex items-center gap-2 border-t border-[#2b2d33] bg-[#18191d] px-3.5 py-2 font-mono text-[.74rem] text-[#a5abb6]">9 symbols <span aria-hidden="true">·</span> TypeScript</p>
</div>
</div>
</div>
CSS
/* Indent guides are a pseudo-element per group, so they live here. */
.ide-group{position:relative}
.ide-group::before{content:"";position:absolute;top:0;bottom:0;left:calc(var(--d) * 14px + 14px);border-left:1px solid #363940;pointer-events:none}
.ide-group:has(>[aria-selected="true"])::before{border-color:#6b717d}
JavaScript
/* WAI-ARIA tree view: one tab stop (roving tabindex), Up/Down move, Right opens or goes to the first child,
   Left closes or goes to the parent, Home/End, type-ahead, Enter/Space open a folder or select a file. */
document.querySelectorAll('[role="tree"][data-tree]').forEach(tree => {
  const all = () => [...tree.querySelectorAll('[role="treeitem"]')];
  const shown = () => all().filter(i => !i.parentElement.closest('[role="treeitem"][aria-expanded="false"]'));
  const isFolder = i => i.hasAttribute('aria-expanded');
  const parentOf = i => i.parentElement.closest('[role="treeitem"]');
  const nameOf = i => i.querySelector(':scope > [data-row] [data-name]').textContent.trim();
  const out = document.getElementById(tree.dataset.status || '');
  let typed = '', timer;
  const focus = i => { all().forEach(n => { n.tabIndex = -1; }); i.tabIndex = 0; i.focus(); };
  const toggle = (i, open = i.getAttribute('aria-expanded') !== 'true') => i.setAttribute('aria-expanded', String(open));
  const select = i => {
    all().forEach(n => { if (!isFolder(n)) n.setAttribute('aria-selected', 'false'); });
    i.setAttribute('aria-selected', 'true');
    if (out) { const p = []; for (let n = i; n; n = parentOf(n)) p.unshift(nameOf(n)); out.textContent = p.join('/'); }
  };
  tree.addEventListener('click', e => {
    const row = e.target.closest('[data-row]'); if (!row) return;
    const i = row.parentElement; focus(i); isFolder(i) ? toggle(i) : select(i);
  });
  tree.addEventListener('keydown', e => {
    const i = e.target.closest('[role="treeitem"]'); if (!i || e.altKey || e.ctrlKey || e.metaKey) return;
    const list = shown(), n = list.indexOf(i), open = i.getAttribute('aria-expanded') === 'true'; let to = null;
    switch (e.key) {
      case 'ArrowDown': to = list[n + 1]; break;
      case 'ArrowUp': to = list[n - 1]; break;
      case 'Home': to = list[0]; break;
      case 'End': to = list[list.length - 1]; break;
      case 'ArrowRight': if (isFolder(i)) open ? to = i.querySelector('[role="treeitem"]') : toggle(i, true); break;
      case 'ArrowLeft': if (isFolder(i) && open) toggle(i, false); else to = parentOf(i); break;
      case 'Enter': case ' ': isFolder(i) ? toggle(i) : select(i); break;
      case '*': [...i.parentElement.children].filter(isFolder).forEach(s => toggle(s, true)); break;
      default: {
        if (e.key.length !== 1 || !/\S/.test(e.key)) return;
        typed += e.key.toLowerCase(); clearTimeout(timer); timer = setTimeout(() => { typed = ''; }, 500);
        const start = typed.length > 1 ? n : n + 1;
        to = [...list.slice(start), ...list.slice(0, start)].find(x => nameOf(x).toLowerCase().startsWith(typed));
      }
    }
    e.preventDefault(); if (to) focus(to);
  });
});

About this set

Two dark trees in the style of a code editor’s side panel. IDE explorer with git status and indent guides shows an API project with modified and untracked markers, a problem count on a file with errors, dots on folders that contain changes and a status bar with the branch name. Symbol outline lists the classes, methods, properties and variables in a TypeScript file, each with its own icon and a line number. Both follow the WAI-ARIA tree view pattern with the same short script as the light file explorer: one tab stop per tree, arrow keys to move, open and close, Home and End, type-ahead, and Enter or Space to select. Every nested group draws a thin indent guide with a pseudo-element, and :has() brightens the guide of the group that holds the selected item and colours file names by their git state, so no extra classes are needed. Status letters such as M and U are hidden from screen readers and replaced with words like modified and untracked. Colours were checked against the dark panel at 4.5:1.

What’s included

  • Dark editor panel with indent guides on every level
  • Git markers, change dots and problem counts
  • Symbol outline with class, method, property and variable icons
  • WAI-ARIA tree keyboard model with roving tabindex and type-ahead
  • Bootstrap version runs in data-bs-theme dark

Accessibility

Each tree has an aria-label, items use the tree, treeitem and group roles, and the selection is exposed with aria-selected. Git letters and counts have visually hidden text such as modified, untracked and 2 problems. The focused row has a 1px blue outline inside the row, all text meets 4.5:1 on the dark panel, and the chevron rotation stops under prefers-reduced-motion.

Customise it

Set --ide-sel, --ide-hover, --ide-focus, --ide-guide, --ide-guide-on and --ide-indent on .ide, and the .c-* classes for icon colours. The git colours are the three .ide-row:has() rules; in Tailwind they are has-[.git-m]: and similar utilities on the row.