Tree View
A Tree View displays hierarchical data with expandable and collapsible nodes. It is used for file explorers, navigation sidebars, category browsers, and org charts. This guide covers production-ready tree patterns in plain HTML/CSS, Tailwind CSS, and Bootstrap — including icons, selection, disabled states, keyboard navigation, and ARIA semantics.
info
A minimal expandable tree. Click the chevron to expand or collapse a branch. CSS drives the caret rotation and group visibility through the aria-expanded attribute.
| 1 | <ul class="tree" role="tree" aria-label="File explorer"> |
| 2 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 3 | <div class="tree-row"> |
| 4 | <button class="toggle" aria-label="Toggle src"> |
| 5 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span class="label"> |
| 10 | src |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group" role="group"> |
| 14 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 15 | <div class="tree-row"> |
| 16 | <button class="toggle" aria-label="Toggle components"> |
| 17 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 18 | <path d="M6 9l6 6 6-6"/> |
| 19 | </svg> |
| 20 | </button> |
| 21 | <span class="label"> |
| 22 | components |
| 23 | </span> |
| 24 | </div> |
| 25 | <ul class="tree-group" role="group"> |
| 26 | <li class="tree-node" role="treeitem"> |
| 27 | <div class="tree-row"> |
| 28 | <span class="spacer"> |
| 29 | </span> |
| 30 | <span class="label"> |
| 31 | Button.tsx |
| 32 | </span> |
| 33 | </div> |
| 34 | </li> |
| 35 | <li class="tree-node" role="treeitem"> |
| 36 | <div class="tree-row"> |
| 37 | <span class="spacer"> |
| 38 | </span> |
| 39 | <span class="label"> |
| 40 | TreeView.tsx |
| 41 | </span> |
| 42 | </div> |
| 43 | </li> |
| 44 | </ul> |
| 45 | </li> |
| 46 | <li class="tree-node" role="treeitem"> |
| 47 | <div class="tree-row"> |
| 48 | <span class="spacer"> |
| 49 | </span> |
| 50 | <span class="label"> |
| 51 | page.tsx |
| 52 | </span> |
| 53 | </div> |
| 54 | </li> |
| 55 | </ul> |
| 56 | </li> |
| 57 | <li class="tree-node" role="treeitem"> |
| 58 | <div class="tree-row"> |
| 59 | <span class="spacer"> |
| 60 | </span> |
| 61 | <span class="label"> |
| 62 | package.json |
| 63 | </span> |
| 64 | </div> |
| 65 | </li> |
| 66 | <li class="tree-node" role="treeitem"> |
| 67 | <div class="tree-row"> |
| 68 | <span class="spacer"> |
| 69 | </span> |
| 70 | <span class="label"> |
| 71 | README.md |
| 72 | </span> |
| 73 | </div> |
| 74 | </li> |
| 75 | </ul> |
| 1 | .tree { |
| 2 | list-style:none; |
| 3 | margin:0; |
| 4 | padding:16px; |
| 5 | font-family:ui-monospace, |
| 6 | SFMono-Regular, |
| 7 | Menlo, |
| 8 | Monaco, |
| 9 | Consolas, |
| 10 | monospace; |
| 11 | font-size:13px; |
| 12 | color:#E0E0E0; |
| 13 | background:#0A0A0A; |
| 14 | border-radius:8px |
| 15 | } |
| 16 | .tree ul { |
| 17 | list-style:none; |
| 18 | margin:0; |
| 19 | padding:0 |
| 20 | } |
| 21 | .tree-row { |
| 22 | display:flex; |
| 23 | align-items:center; |
| 24 | gap:6px; |
| 25 | padding:5px 8px; |
| 26 | border-radius:6px; |
| 27 | cursor:pointer |
| 28 | } |
| 29 | .tree-row:hover { |
| 30 | background:rgba(255, |
| 31 | 255, |
| 32 | 255, |
| 33 | .05) |
| 34 | } |
| 35 | .toggle { |
| 36 | display:inline-flex; |
| 37 | align-items:center; |
| 38 | justify-content:center; |
| 39 | width:16px; |
| 40 | height:16px; |
| 41 | padding:0; |
| 42 | border:none; |
| 43 | background:transparent; |
| 44 | color:#808080; |
| 45 | cursor:pointer |
| 46 | } |
| 47 | .caret { |
| 48 | width:10px; |
| 49 | height:10px; |
| 50 | transition:transform .2s |
| 51 | } |
| 52 | .tree-node[aria-expanded="false"] .caret { |
| 53 | transform:rotate(-90deg) |
| 54 | } |
| 55 | .spacer { |
| 56 | width:16px |
| 57 | } |
| 58 | .tree-group { |
| 59 | padding-left:16px; |
| 60 | display:block |
| 61 | } |
| 62 | .tree-node[aria-expanded="false"] > .tree-group { |
| 63 | display:none |
| 64 | } |
| 1 | document.querySelectorAll('.tree-node > .tree-row > .toggle').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const node=btn.closest('.tree-node');const expanded=node.getAttribute('aria-expanded')==='true';node.setAttribute('aria-expanded',String(!expanded))})}) |
| 1 | <ul class="list-none m-0 p-4 font-mono text-[13px] text-[#E0E0E0] bg-[#0A0A0A] rounded-lg" role="tree" aria-label="File explorer"> |
| 2 | <li class="tree-node" aria-expanded="true"> |
| 3 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 4 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle src"> |
| 5 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span> |
| 10 | src |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 14 | <li class="tree-node" aria-expanded="true"> |
| 15 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 16 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle components"> |
| 17 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 18 | <path d="M6 9l6 6 6-6"/> |
| 19 | </svg> |
| 20 | </button> |
| 21 | <span> |
| 22 | components |
| 23 | </span> |
| 24 | </div> |
| 25 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 26 | <li class="tree-node"> |
| 27 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 28 | <span class="w-4"> |
| 29 | </span> |
| 30 | <span> |
| 31 | Button.tsx |
| 32 | </span> |
| 33 | </div> |
| 34 | </li> |
| 35 | <li class="tree-node"> |
| 36 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 37 | <span class="w-4"> |
| 38 | </span> |
| 39 | <span> |
| 40 | TreeView.tsx |
| 41 | </span> |
| 42 | </div> |
| 43 | </li> |
| 44 | </ul> |
| 45 | </li> |
| 46 | <li class="tree-node"> |
| 47 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 48 | <span class="w-4"> |
| 49 | </span> |
| 50 | <span> |
| 51 | page.tsx |
| 52 | </span> |
| 53 | </div> |
| 54 | </li> |
| 55 | </ul> |
| 56 | </li> |
| 57 | <li class="tree-node"> |
| 58 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 59 | <span class="w-4"> |
| 60 | </span> |
| 61 | <span> |
| 62 | package.json |
| 63 | </span> |
| 64 | </div> |
| 65 | </li> |
| 66 | <li class="tree-node"> |
| 67 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 68 | <span class="w-4"> |
| 69 | </span> |
| 70 | <span> |
| 71 | README.md |
| 72 | </span> |
| 73 | </div> |
| 74 | </li> |
| 75 | </ul> |
| 1 | <div class="p-4" style="background:#0A0A0A;border-radius:8px"> |
| 2 | <style> |
| 3 | .bstree{list-style:none;margin:0;padding:0;font-family:var(--bs-font-monospace);font-size:13px;color:#E0E0E0} |
| 4 | .bstree ul{list-style:none;margin:0;padding:0;padding-left:16px} |
| 5 | .brow{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:6px;cursor:pointer} |
| 6 | .brow:hover{background:rgba(255,255,255,.05)} |
| 7 | .btoggle{width:16px;height:16px;padding:0;border:none;background:transparent;color:#808080;display:inline-flex;align-items:center;justify-content:center} |
| 8 | .bcaret{width:10px;height:10px;transition:transform .2s} |
| 9 | .bnode[aria-expanded="false"] .bcaret{transform:rotate(-90deg)} |
| 10 | .bnode[aria-expanded="false"] > ul{display:none} |
| 11 | </style> |
| 12 | <ul class="bstree" role="tree" aria-label="File explorer"> |
| 13 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 14 | <div class="brow"> |
| 15 | <button class="btoggle" aria-label="Toggle src"> |
| 16 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 17 | <path d="M6 9l6 6 6-6"/> |
| 18 | </svg> |
| 19 | </button> |
| 20 | <span> |
| 21 | src |
| 22 | </span> |
| 23 | </div> |
| 24 | <ul role="group"> |
| 25 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 26 | <div class="brow"> |
| 27 | <button class="btoggle" aria-label="Toggle components"> |
| 28 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 29 | <path d="M6 9l6 6 6-6"/> |
| 30 | </svg> |
| 31 | </button> |
| 32 | <span> |
| 33 | components |
| 34 | </span> |
| 35 | </div> |
| 36 | <ul role="group"> |
| 37 | <li class="bnode" role="treeitem"> |
| 38 | <div class="brow"> |
| 39 | <span class="btoggle"> |
| 40 | </span> |
| 41 | <span> |
| 42 | Button.tsx |
| 43 | </span> |
| 44 | </div> |
| 45 | </li> |
| 46 | <li class="bnode" role="treeitem"> |
| 47 | <div class="brow"> |
| 48 | <span class="btoggle"> |
| 49 | </span> |
| 50 | <span> |
| 51 | TreeView.tsx |
| 52 | </span> |
| 53 | </div> |
| 54 | </li> |
| 55 | </ul> |
| 56 | </li> |
| 57 | <li class="bnode" role="treeitem"> |
| 58 | <div class="brow"> |
| 59 | <span class="btoggle"> |
| 60 | </span> |
| 61 | <span> |
| 62 | page.tsx |
| 63 | </span> |
| 64 | </div> |
| 65 | </li> |
| 66 | </ul> |
| 67 | </li> |
| 68 | <li class="bnode" role="treeitem"> |
| 69 | <div class="brow"> |
| 70 | <span class="btoggle"> |
| 71 | </span> |
| 72 | <span> |
| 73 | package.json |
| 74 | </span> |
| 75 | </div> |
| 76 | </li> |
| 77 | <li class="bnode" role="treeitem"> |
| 78 | <div class="brow"> |
| 79 | <span class="btoggle"> |
| 80 | </span> |
| 81 | <span> |
| 82 | README.md |
| 83 | </span> |
| 84 | </div> |
| 85 | </li> |
| 86 | </ul> |
| 87 | </div> |
Inline SVG folder and file icons make the hierarchy scannable. The caret stays neutral while folder and file icons add color cues.
| 1 | <ul class="tree" role="tree" aria-label="File explorer"> |
| 2 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 3 | <div class="tree-row"> |
| 4 | <button class="toggle" aria-label="Toggle src"> |
| 5 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <svg class="icon folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 10 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 11 | </svg> |
| 12 | <span class="label"> |
| 13 | src |
| 14 | </span> |
| 15 | </div> |
| 16 | <ul class="tree-group" role="group"> |
| 17 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 18 | <div class="tree-row"> |
| 19 | <button class="toggle" aria-label="Toggle components"> |
| 20 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 21 | <path d="M6 9l6 6 6-6"/> |
| 22 | </svg> |
| 23 | </button> |
| 24 | <svg class="icon folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 25 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 26 | </svg> |
| 27 | <span class="label"> |
| 28 | components |
| 29 | </span> |
| 30 | </div> |
| 31 | <ul class="tree-group" role="group"> |
| 32 | <li class="tree-node" role="treeitem"> |
| 33 | <div class="tree-row"> |
| 34 | <span class="spacer"> |
| 35 | </span> |
| 36 | <svg class="icon file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 37 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 38 | <polyline points="14 2 14 8 20 8"/> |
| 39 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 40 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 41 | <polyline points="10 9 9 9 8 9"/> |
| 42 | </svg> |
| 43 | <span class="label"> |
| 44 | Button.tsx |
| 45 | </span> |
| 46 | </div> |
| 47 | </li> |
| 48 | <li class="tree-node" role="treeitem"> |
| 49 | <div class="tree-row"> |
| 50 | <span class="spacer"> |
| 51 | </span> |
| 52 | <svg class="icon file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 53 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 54 | <polyline points="14 2 14 8 20 8"/> |
| 55 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 56 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 57 | <polyline points="10 9 9 9 8 9"/> |
| 58 | </svg> |
| 59 | <span class="label"> |
| 60 | TreeView.tsx |
| 61 | </span> |
| 62 | </div> |
| 63 | </li> |
| 64 | </ul> |
| 65 | </li> |
| 66 | <li class="tree-node" role="treeitem"> |
| 67 | <div class="tree-row"> |
| 68 | <span class="spacer"> |
| 69 | </span> |
| 70 | <svg class="icon file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 71 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 72 | <polyline points="14 2 14 8 20 8"/> |
| 73 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 74 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 75 | <polyline points="10 9 9 9 8 9"/> |
| 76 | </svg> |
| 77 | <span class="label"> |
| 78 | page.tsx |
| 79 | </span> |
| 80 | </div> |
| 81 | </li> |
| 82 | </ul> |
| 83 | </li> |
| 84 | <li class="tree-node" role="treeitem"> |
| 85 | <div class="tree-row"> |
| 86 | <span class="spacer"> |
| 87 | </span> |
| 88 | <svg class="icon file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 89 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 90 | <polyline points="14 2 14 8 20 8"/> |
| 91 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 92 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 93 | <polyline points="10 9 9 9 8 9"/> |
| 94 | </svg> |
| 95 | <span class="label"> |
| 96 | package.json |
| 97 | </span> |
| 98 | </div> |
| 99 | </li> |
| 100 | </ul> |
| 1 | .tree { |
| 2 | list-style:none; |
| 3 | margin:0; |
| 4 | padding:16px; |
| 5 | font-family:ui-monospace, |
| 6 | SFMono-Regular, |
| 7 | Menlo, |
| 8 | Monaco, |
| 9 | Consolas, |
| 10 | monospace; |
| 11 | font-size:13px; |
| 12 | color:#E0E0E0; |
| 13 | background:#0A0A0A; |
| 14 | border-radius:8px |
| 15 | } |
| 16 | .tree ul { |
| 17 | list-style:none; |
| 18 | margin:0; |
| 19 | padding:0 |
| 20 | } |
| 21 | .tree-row { |
| 22 | display:flex; |
| 23 | align-items:center; |
| 24 | gap:6px; |
| 25 | padding:5px 8px; |
| 26 | border-radius:6px; |
| 27 | cursor:pointer |
| 28 | } |
| 29 | .tree-row:hover { |
| 30 | background:rgba(255, |
| 31 | 255, |
| 32 | 255, |
| 33 | .05) |
| 34 | } |
| 35 | .toggle { |
| 36 | display:inline-flex; |
| 37 | align-items:center; |
| 38 | justify-content:center; |
| 39 | width:16px; |
| 40 | height:16px; |
| 41 | padding:0; |
| 42 | border:none; |
| 43 | background:transparent; |
| 44 | color:#808080; |
| 45 | cursor:pointer |
| 46 | } |
| 47 | .caret { |
| 48 | width:10px; |
| 49 | height:10px; |
| 50 | transition:transform .2s |
| 51 | } |
| 52 | .tree-node[aria-expanded="false"] .caret { |
| 53 | transform:rotate(-90deg) |
| 54 | } |
| 55 | .spacer { |
| 56 | width:16px |
| 57 | } |
| 58 | .icon { |
| 59 | width:16px; |
| 60 | height:16px; |
| 61 | flex-shrink:0 |
| 62 | } |
| 63 | .folder { |
| 64 | color:#EAB308 |
| 65 | } |
| 66 | .file { |
| 67 | color:#A0A0A0 |
| 68 | } |
| 69 | .tree-group { |
| 70 | padding-left:16px; |
| 71 | display:block |
| 72 | } |
| 73 | .tree-node[aria-expanded="false"] > .tree-group { |
| 74 | display:none |
| 75 | } |
| 1 | document.querySelectorAll('.tree-node > .tree-row > .toggle').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const node=btn.closest('.tree-node');const expanded=node.getAttribute('aria-expanded')==='true';node.setAttribute('aria-expanded',String(!expanded))})}) |
| 1 | <ul class="list-none m-0 p-4 font-mono text-[13px] text-[#E0E0E0] bg-[#0A0A0A] rounded-lg" role="tree" aria-label="File explorer"> |
| 2 | <li class="tree-node" aria-expanded="true"> |
| 3 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 4 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle src"> |
| 5 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <svg class="w-4 h-4 shrink-0 text-[#EAB308]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 10 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 11 | </svg> |
| 12 | <span> |
| 13 | src |
| 14 | </span> |
| 15 | </div> |
| 16 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 17 | <li class="tree-node" aria-expanded="true"> |
| 18 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 19 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle components"> |
| 20 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 21 | <path d="M6 9l6 6 6-6"/> |
| 22 | </svg> |
| 23 | </button> |
| 24 | <svg class="w-4 h-4 shrink-0 text-[#EAB308]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 25 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 26 | </svg> |
| 27 | <span> |
| 28 | components |
| 29 | </span> |
| 30 | </div> |
| 31 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 32 | <li class="tree-node"> |
| 33 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 34 | <span class="w-4"> |
| 35 | </span> |
| 36 | <svg class="w-4 h-4 shrink-0 text-[#A0A0A0]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 37 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 38 | <polyline points="14 2 14 8 20 8"/> |
| 39 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 40 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 41 | <polyline points="10 9 9 9 8 9"/> |
| 42 | </svg> |
| 43 | <span> |
| 44 | Button.tsx |
| 45 | </span> |
| 46 | </div> |
| 47 | </li> |
| 48 | <li class="tree-node"> |
| 49 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 50 | <span class="w-4"> |
| 51 | </span> |
| 52 | <svg class="w-4 h-4 shrink-0 text-[#A0A0A0]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 53 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 54 | <polyline points="14 2 14 8 20 8"/> |
| 55 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 56 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 57 | <polyline points="10 9 9 9 8 9"/> |
| 58 | </svg> |
| 59 | <span> |
| 60 | TreeView.tsx |
| 61 | </span> |
| 62 | </div> |
| 63 | </li> |
| 64 | </ul> |
| 65 | </li> |
| 66 | <li class="tree-node"> |
| 67 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 68 | <span class="w-4"> |
| 69 | </span> |
| 70 | <svg class="w-4 h-4 shrink-0 text-[#A0A0A0]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 71 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 72 | <polyline points="14 2 14 8 20 8"/> |
| 73 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 74 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 75 | <polyline points="10 9 9 9 8 9"/> |
| 76 | </svg> |
| 77 | <span> |
| 78 | page.tsx |
| 79 | </span> |
| 80 | </div> |
| 81 | </li> |
| 82 | </ul> |
| 83 | </li> |
| 84 | <li class="tree-node"> |
| 85 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 86 | <span class="w-4"> |
| 87 | </span> |
| 88 | <svg class="w-4 h-4 shrink-0 text-[#A0A0A0]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 89 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 90 | <polyline points="14 2 14 8 20 8"/> |
| 91 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 92 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 93 | <polyline points="10 9 9 9 8 9"/> |
| 94 | </svg> |
| 95 | <span> |
| 96 | package.json |
| 97 | </span> |
| 98 | </div> |
| 99 | </li> |
| 100 | </ul> |
| 1 | <div class="p-4" style="background:#0A0A0A;border-radius:8px"> |
| 2 | <style> |
| 3 | .bstree{list-style:none;margin:0;padding:0;font-family:var(--bs-font-monospace);font-size:13px;color:#E0E0E0} |
| 4 | .bstree ul{list-style:none;margin:0;padding:0;padding-left:16px} |
| 5 | .brow{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:6px;cursor:pointer} |
| 6 | .brow:hover{background:rgba(255,255,255,.05)} |
| 7 | .btoggle{width:16px;height:16px;padding:0;border:none;background:transparent;color:#808080;display:inline-flex;align-items:center;justify-content:center} |
| 8 | .bcaret{width:10px;height:10px;transition:transform .2s} |
| 9 | .bicon{width:16px;height:16px;flex-shrink:0} |
| 10 | .bfolder{color:#EAB308} |
| 11 | .bfile{color:#A0A0A0} |
| 12 | .bnode[aria-expanded="false"] .bcaret{transform:rotate(-90deg)} |
| 13 | .bnode[aria-expanded="false"] > ul{display:none} |
| 14 | </style> |
| 15 | <ul class="bstree" role="tree" aria-label="File explorer"> |
| 16 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 17 | <div class="brow"> |
| 18 | <button class="btoggle" aria-label="Toggle src"> |
| 19 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 20 | <path d="M6 9l6 6 6-6"/> |
| 21 | </svg> |
| 22 | </button> |
| 23 | <svg class="bicon bfolder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 24 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 25 | </svg> |
| 26 | <span> |
| 27 | src |
| 28 | </span> |
| 29 | </div> |
| 30 | <ul role="group"> |
| 31 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 32 | <div class="brow"> |
| 33 | <button class="btoggle" aria-label="Toggle components"> |
| 34 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 35 | <path d="M6 9l6 6 6-6"/> |
| 36 | </svg> |
| 37 | </button> |
| 38 | <svg class="bicon bfolder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 39 | <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/> |
| 40 | </svg> |
| 41 | <span> |
| 42 | components |
| 43 | </span> |
| 44 | </div> |
| 45 | <ul role="group"> |
| 46 | <li class="bnode" role="treeitem"> |
| 47 | <div class="brow"> |
| 48 | <span class="btoggle"> |
| 49 | </span> |
| 50 | <svg class="bicon bfile" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 51 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 52 | <polyline points="14 2 14 8 20 8"/> |
| 53 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 54 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 55 | <polyline points="10 9 9 9 8 9"/> |
| 56 | </svg> |
| 57 | <span> |
| 58 | Button.tsx |
| 59 | </span> |
| 60 | </div> |
| 61 | </li> |
| 62 | <li class="bnode" role="treeitem"> |
| 63 | <div class="brow"> |
| 64 | <span class="btoggle"> |
| 65 | </span> |
| 66 | <svg class="bicon bfile" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 67 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 68 | <polyline points="14 2 14 8 20 8"/> |
| 69 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 70 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 71 | <polyline points="10 9 9 9 8 9"/> |
| 72 | </svg> |
| 73 | <span> |
| 74 | TreeView.tsx |
| 75 | </span> |
| 76 | </div> |
| 77 | </li> |
| 78 | </ul> |
| 79 | </li> |
| 80 | <li class="bnode" role="treeitem"> |
| 81 | <div class="brow"> |
| 82 | <span class="btoggle"> |
| 83 | </span> |
| 84 | <svg class="bicon bfile" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 85 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 86 | <polyline points="14 2 14 8 20 8"/> |
| 87 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 88 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 89 | <polyline points="10 9 9 9 8 9"/> |
| 90 | </svg> |
| 91 | <span> |
| 92 | page.tsx |
| 93 | </span> |
| 94 | </div> |
| 95 | </li> |
| 96 | </ul> |
| 97 | </li> |
| 98 | <li class="bnode" role="treeitem"> |
| 99 | <div class="brow"> |
| 100 | <span class="btoggle"> |
| 101 | </span> |
| 102 | <svg class="bicon bfile" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 103 | <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 104 | <polyline points="14 2 14 8 20 8"/> |
| 105 | <line x1="16" y1="13" x2="8" y2="13"/> |
| 106 | <line x1="16" y1="17" x2="8" y2="17"/> |
| 107 | <polyline points="10 9 9 9 8 9"/> |
| 108 | </svg> |
| 109 | <span> |
| 110 | package.json |
| 111 | </span> |
| 112 | </div> |
| 113 | </li> |
| 114 | </ul> |
| 115 | </div> |
Single-select trees use aria-selected to communicate the active node. Click a row to select it; the green accent matches the ForgeLearn terminal aesthetic.
| 1 | <ul class="tree" role="tree" aria-label="Project settings"> |
| 2 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 3 | <div class="tree-row"> |
| 4 | <button class="toggle" aria-label="Toggle project"> |
| 5 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span class="label"> |
| 10 | forgelearn |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group" role="group"> |
| 14 | <li class="tree-node" role="treeitem" aria-selected="true"> |
| 15 | <div class="tree-row"> |
| 16 | <span class="spacer"> |
| 17 | </span> |
| 18 | <span class="label"> |
| 19 | General |
| 20 | </span> |
| 21 | </div> |
| 22 | </li> |
| 23 | <li class="tree-node" role="treeitem" aria-selected="false"> |
| 24 | <div class="tree-row"> |
| 25 | <span class="spacer"> |
| 26 | </span> |
| 27 | <span class="label"> |
| 28 | Members |
| 29 | </span> |
| 30 | </div> |
| 31 | </li> |
| 32 | <li class="tree-node" role="treeitem" aria-selected="false"> |
| 33 | <div class="tree-row"> |
| 34 | <span class="spacer"> |
| 35 | </span> |
| 36 | <span class="label"> |
| 37 | Billing |
| 38 | </span> |
| 39 | </div> |
| 40 | </li> |
| 41 | </ul> |
| 42 | </li> |
| 43 | <li class="tree-node" role="treeitem" aria-expanded="false"> |
| 44 | <div class="tree-row"> |
| 45 | <button class="toggle" aria-label="Toggle archived"> |
| 46 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 47 | <path d="M6 9l6 6 6-6"/> |
| 48 | </svg> |
| 49 | </button> |
| 50 | <span class="label"> |
| 51 | Archived |
| 52 | </span> |
| 53 | </div> |
| 54 | <ul class="tree-group" role="group"> |
| 55 | <li class="tree-node" role="treeitem" aria-selected="false"> |
| 56 | <div class="tree-row"> |
| 57 | <span class="spacer"> |
| 58 | </span> |
| 59 | <span class="label"> |
| 60 | v1-docs |
| 61 | </span> |
| 62 | </div> |
| 63 | </li> |
| 64 | </ul> |
| 65 | </li> |
| 66 | </ul> |
| 1 | .tree { |
| 2 | list-style:none; |
| 3 | margin:0; |
| 4 | padding:16px; |
| 5 | font-family:ui-monospace, |
| 6 | SFMono-Regular, |
| 7 | Menlo, |
| 8 | Monaco, |
| 9 | Consolas, |
| 10 | monospace; |
| 11 | font-size:13px; |
| 12 | color:#E0E0E0; |
| 13 | background:#0A0A0A; |
| 14 | border-radius:8px |
| 15 | } |
| 16 | .tree ul { |
| 17 | list-style:none; |
| 18 | margin:0; |
| 19 | padding:0 |
| 20 | } |
| 21 | .tree-row { |
| 22 | display:flex; |
| 23 | align-items:center; |
| 24 | gap:6px; |
| 25 | padding:5px 8px; |
| 26 | border-radius:6px; |
| 27 | cursor:pointer |
| 28 | } |
| 29 | .tree-row:hover { |
| 30 | background:rgba(255, |
| 31 | 255, |
| 32 | 255, |
| 33 | .05) |
| 34 | } |
| 35 | .toggle { |
| 36 | display:inline-flex; |
| 37 | align-items:center; |
| 38 | justify-content:center; |
| 39 | width:16px; |
| 40 | height:16px; |
| 41 | padding:0; |
| 42 | border:none; |
| 43 | background:transparent; |
| 44 | color:#808080; |
| 45 | cursor:pointer |
| 46 | } |
| 47 | .caret { |
| 48 | width:10px; |
| 49 | height:10px; |
| 50 | transition:transform .2s |
| 51 | } |
| 52 | .tree-node[aria-expanded="false"] .caret { |
| 53 | transform:rotate(-90deg) |
| 54 | } |
| 55 | .spacer { |
| 56 | width:16px |
| 57 | } |
| 58 | .tree-group { |
| 59 | padding-left:16px; |
| 60 | display:block |
| 61 | } |
| 62 | .tree-node[aria-expanded="false"] > .tree-group { |
| 63 | display:none |
| 64 | } |
| 65 | .tree-node[aria-selected="true"] > .tree-row { |
| 66 | background:rgba(0, |
| 67 | 255, |
| 68 | 65, |
| 69 | .12); |
| 70 | color:#00FF41 |
| 71 | } |
| 1 | document.querySelectorAll('.tree-node > .tree-row > .toggle').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const node=btn.closest('.tree-node');const expanded=node.getAttribute('aria-expanded')==='true';node.setAttribute('aria-expanded',String(!expanded))})});document.querySelectorAll('.tree-row').forEach(row=>{row.addEventListener('click',e=>{if(e.target.closest('.toggle'))return;const tree=row.closest('[role="tree"]');tree.querySelectorAll('[aria-selected="true"]').forEach(n=>n.setAttribute('aria-selected','false'));const node=row.closest('.tree-node');if(node)node.setAttribute('aria-selected','true')})}) |
| 1 | <ul class="list-none m-0 p-4 font-mono text-[13px] text-[#E0E0E0] bg-[#0A0A0A] rounded-lg" role="tree" aria-label="Project settings"> |
| 2 | <li class="tree-node" aria-expanded="true"> |
| 3 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 4 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle project"> |
| 5 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span> |
| 10 | forgelearn |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 14 | <li class="tree-node" aria-selected="true"> |
| 15 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer bg-[rgba(0,255,65,0.12)] text-[#00FF41]"> |
| 16 | <span class="w-4"> |
| 17 | </span> |
| 18 | <span> |
| 19 | General |
| 20 | </span> |
| 21 | </div> |
| 22 | </li> |
| 23 | <li class="tree-node" aria-selected="false"> |
| 24 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 25 | <span class="w-4"> |
| 26 | </span> |
| 27 | <span> |
| 28 | Members |
| 29 | </span> |
| 30 | </div> |
| 31 | </li> |
| 32 | <li class="tree-node" aria-selected="false"> |
| 33 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 34 | <span class="w-4"> |
| 35 | </span> |
| 36 | <span> |
| 37 | Billing |
| 38 | </span> |
| 39 | </div> |
| 40 | </li> |
| 41 | </ul> |
| 42 | </li> |
| 43 | <li class="tree-node" aria-expanded="false"> |
| 44 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 45 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle archived"> |
| 46 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200 rotate-[-90deg]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 47 | <path d="M6 9l6 6 6-6"/> |
| 48 | </svg> |
| 49 | </button> |
| 50 | <span> |
| 51 | Archived |
| 52 | </span> |
| 53 | </div> |
| 54 | <ul class="tree-group list-none m-0 pl-4 hidden" role="group"> |
| 55 | <li class="tree-node" aria-selected="false"> |
| 56 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 57 | <span class="w-4"> |
| 58 | </span> |
| 59 | <span> |
| 60 | v1-docs |
| 61 | </span> |
| 62 | </div> |
| 63 | </li> |
| 64 | </ul> |
| 65 | </li> |
| 66 | </ul> |
| 1 | <div class="p-4" style="background:#0A0A0A;border-radius:8px"> |
| 2 | <style> |
| 3 | .bstree{list-style:none;margin:0;padding:0;font-family:var(--bs-font-monospace);font-size:13px;color:#E0E0E0} |
| 4 | .bstree ul{list-style:none;margin:0;padding:0;padding-left:16px} |
| 5 | .brow{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:6px;cursor:pointer} |
| 6 | .brow:hover{background:rgba(255,255,255,.05)} |
| 7 | .btoggle{width:16px;height:16px;padding:0;border:none;background:transparent;color:#808080;display:inline-flex;align-items:center;justify-content:center} |
| 8 | .bcaret{width:10px;height:10px;transition:transform .2s} |
| 9 | .bnode[aria-expanded="false"] .bcaret{transform:rotate(-90deg)} |
| 10 | .bnode[aria-expanded="false"] > ul{display:none} |
| 11 | .bnode[aria-selected="true"] > .brow{background:rgba(0,255,65,.12);color:#00FF41} |
| 12 | </style> |
| 13 | <ul class="bstree" role="tree" aria-label="Project settings"> |
| 14 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 15 | <div class="brow"> |
| 16 | <button class="btoggle" aria-label="Toggle project"> |
| 17 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 18 | <path d="M6 9l6 6 6-6"/> |
| 19 | </svg> |
| 20 | </button> |
| 21 | <span> |
| 22 | forgelearn |
| 23 | </span> |
| 24 | </div> |
| 25 | <ul role="group"> |
| 26 | <li class="bnode" role="treeitem" aria-selected="true"> |
| 27 | <div class="brow"> |
| 28 | <span class="btoggle"> |
| 29 | </span> |
| 30 | <span> |
| 31 | General |
| 32 | </span> |
| 33 | </div> |
| 34 | </li> |
| 35 | <li class="bnode" role="treeitem" aria-selected="false"> |
| 36 | <div class="brow"> |
| 37 | <span class="btoggle"> |
| 38 | </span> |
| 39 | <span> |
| 40 | Members |
| 41 | </span> |
| 42 | </div> |
| 43 | </li> |
| 44 | <li class="bnode" role="treeitem" aria-selected="false"> |
| 45 | <div class="brow"> |
| 46 | <span class="btoggle"> |
| 47 | </span> |
| 48 | <span> |
| 49 | Billing |
| 50 | </span> |
| 51 | </div> |
| 52 | </li> |
| 53 | </ul> |
| 54 | </li> |
| 55 | <li class="bnode" role="treeitem" aria-expanded="false"> |
| 56 | <div class="brow"> |
| 57 | <button class="btoggle" aria-label="Toggle archived"> |
| 58 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 59 | <path d="M6 9l6 6 6-6"/> |
| 60 | </svg> |
| 61 | </button> |
| 62 | <span> |
| 63 | Archived |
| 64 | </span> |
| 65 | </div> |
| 66 | <ul role="group"> |
| 67 | <li class="bnode" role="treeitem" aria-selected="false"> |
| 68 | <div class="brow"> |
| 69 | <span class="btoggle"> |
| 70 | </span> |
| 71 | <span> |
| 72 | v1-docs |
| 73 | </span> |
| 74 | </div> |
| 75 | </li> |
| 76 | </ul> |
| 77 | </li> |
| 78 | </ul> |
| 79 | </div> |
Nodes that are not interactive should set aria-disabled="true", remove the caret, and use muted colors. Disabled branches can still be expanded if their parent is enabled.
| 1 | <ul class="tree" role="tree" aria-label="Permissions"> |
| 2 | <li class="tree-node" role="treeitem" aria-expanded="true"> |
| 3 | <div class="tree-row"> |
| 4 | <button class="toggle" aria-label="Toggle organization"> |
| 5 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span class="label"> |
| 10 | Organization |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group" role="group"> |
| 14 | <li class="tree-node" role="treeitem" aria-selected="false"> |
| 15 | <div class="tree-row"> |
| 16 | <span class="spacer"> |
| 17 | </span> |
| 18 | <span class="label"> |
| 19 | Admins |
| 20 | </span> |
| 21 | </div> |
| 22 | </li> |
| 23 | <li class="tree-node" role="treeitem" aria-disabled="true"> |
| 24 | <div class="tree-row disabled"> |
| 25 | <span class="spacer"> |
| 26 | </span> |
| 27 | <span class="label"> |
| 28 | Guests (read-only) |
| 29 | </span> |
| 30 | </div> |
| 31 | </li> |
| 32 | </ul> |
| 33 | </li> |
| 34 | <li class="tree-node" role="treeitem" aria-expanded="false" aria-disabled="true"> |
| 35 | <div class="tree-row disabled"> |
| 36 | <button class="toggle" aria-label="Toggle legacy" disabled> |
| 37 | <svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 38 | <path d="M6 9l6 6 6-6"/> |
| 39 | </svg> |
| 40 | </button> |
| 41 | <span class="label"> |
| 42 | Legacy Projects |
| 43 | </span> |
| 44 | </div> |
| 45 | <ul class="tree-group" role="group"> |
| 46 | <li class="tree-node" role="treeitem"> |
| 47 | <div class="tree-row disabled"> |
| 48 | <span class="spacer"> |
| 49 | </span> |
| 50 | <span class="label"> |
| 51 | v0-archive |
| 52 | </span> |
| 53 | </div> |
| 54 | </li> |
| 55 | </ul> |
| 56 | </li> |
| 57 | </ul> |
| 1 | .tree { |
| 2 | list-style:none; |
| 3 | margin:0; |
| 4 | padding:16px; |
| 5 | font-family:ui-monospace, |
| 6 | SFMono-Regular, |
| 7 | Menlo, |
| 8 | Monaco, |
| 9 | Consolas, |
| 10 | monospace; |
| 11 | font-size:13px; |
| 12 | color:#E0E0E0; |
| 13 | background:#0A0A0A; |
| 14 | border-radius:8px |
| 15 | } |
| 16 | .tree ul { |
| 17 | list-style:none; |
| 18 | margin:0; |
| 19 | padding:0 |
| 20 | } |
| 21 | .tree-row { |
| 22 | display:flex; |
| 23 | align-items:center; |
| 24 | gap:6px; |
| 25 | padding:5px 8px; |
| 26 | border-radius:6px; |
| 27 | cursor:pointer |
| 28 | } |
| 29 | .tree-row:hover:not(.disabled) { |
| 30 | background:rgba(255, |
| 31 | 255, |
| 32 | 255, |
| 33 | .05) |
| 34 | } |
| 35 | .toggle { |
| 36 | display:inline-flex; |
| 37 | align-items:center; |
| 38 | justify-content:center; |
| 39 | width:16px; |
| 40 | height:16px; |
| 41 | padding:0; |
| 42 | border:none; |
| 43 | background:transparent; |
| 44 | color:#808080; |
| 45 | cursor:pointer |
| 46 | } |
| 47 | .toggle:disabled { |
| 48 | cursor:not-allowed; |
| 49 | color:#525252 |
| 50 | } |
| 51 | .caret { |
| 52 | width:10px; |
| 53 | height:10px; |
| 54 | transition:transform .2s |
| 55 | } |
| 56 | .tree-node[aria-expanded="false"] .caret { |
| 57 | transform:rotate(-90deg) |
| 58 | } |
| 59 | .spacer { |
| 60 | width:16px |
| 61 | } |
| 62 | .tree-group { |
| 63 | padding-left:16px; |
| 64 | display:block |
| 65 | } |
| 66 | .tree-node[aria-expanded="false"] > .tree-group { |
| 67 | display:none |
| 68 | } |
| 69 | .tree-row.disabled, |
| 70 | .tree-node[aria-disabled="true"] > .tree-row { |
| 71 | color:#525252; |
| 72 | cursor:not-allowed |
| 73 | } |
| 1 | document.querySelectorAll('.tree-node > .tree-row > .toggle').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();if(btn.disabled)return;const node=btn.closest('.tree-node');const expanded=node.getAttribute('aria-expanded')==='true';node.setAttribute('aria-expanded',String(!expanded))})}) |
| 1 | <ul class="list-none m-0 p-4 font-mono text-[13px] text-[#E0E0E0] bg-[#0A0A0A] rounded-lg" role="tree" aria-label="Permissions"> |
| 2 | <li class="tree-node" aria-expanded="true"> |
| 3 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 4 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#808080] cursor-pointer" aria-label="Toggle organization"> |
| 5 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 6 | <path d="M6 9l6 6 6-6"/> |
| 7 | </svg> |
| 8 | </button> |
| 9 | <span> |
| 10 | Organization |
| 11 | </span> |
| 12 | </div> |
| 13 | <ul class="tree-group list-none m-0 pl-4" role="group"> |
| 14 | <li class="tree-node"> |
| 15 | <div class="tree-row flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-white/5 cursor-pointer"> |
| 16 | <span class="w-4"> |
| 17 | </span> |
| 18 | <span> |
| 19 | Admins |
| 20 | </span> |
| 21 | </div> |
| 22 | </li> |
| 23 | <li class="tree-node" aria-disabled="true"> |
| 24 | <div class="tree-row disabled flex items-center gap-1.5 px-2 py-1 rounded-md cursor-not-allowed text-[#525252]"> |
| 25 | <span class="w-4"> |
| 26 | </span> |
| 27 | <span> |
| 28 | Guests (read-only) |
| 29 | </span> |
| 30 | </div> |
| 31 | </li> |
| 32 | </ul> |
| 33 | </li> |
| 34 | <li class="tree-node" aria-expanded="false" aria-disabled="true"> |
| 35 | <div class="tree-row disabled flex items-center gap-1.5 px-2 py-1 rounded-md cursor-not-allowed text-[#525252]"> |
| 36 | <button class="toggle inline-flex items-center justify-center w-4 h-4 bg-transparent border-0 text-[#525252] cursor-not-allowed" aria-label="Toggle legacy" disabled> |
| 37 | <svg class="caret w-2.5 h-2.5 transition-transform duration-200 rotate-[-90deg]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 38 | <path d="M6 9l6 6 6-6"/> |
| 39 | </svg> |
| 40 | </button> |
| 41 | <span> |
| 42 | Legacy Projects |
| 43 | </span> |
| 44 | </div> |
| 45 | <ul class="tree-group list-none m-0 pl-4 hidden" role="group"> |
| 46 | <li class="tree-node"> |
| 47 | <div class="tree-row disabled flex items-center gap-1.5 px-2 py-1 rounded-md cursor-not-allowed text-[#525252]"> |
| 48 | <span class="w-4"> |
| 49 | </span> |
| 50 | <span> |
| 51 | v0-archive |
| 52 | </span> |
| 53 | </div> |
| 54 | </li> |
| 55 | </ul> |
| 56 | </li> |
| 57 | </ul> |
| 1 | <div class="p-4" style="background:#0A0A0A;border-radius:8px"> |
| 2 | <style> |
| 3 | .bstree{list-style:none;margin:0;padding:0;font-family:var(--bs-font-monospace);font-size:13px;color:#E0E0E0} |
| 4 | .bstree ul{list-style:none;margin:0;padding:0;padding-left:16px} |
| 5 | .brow{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:6px;cursor:pointer} |
| 6 | .brow:hover:not(.disabled){background:rgba(255,255,255,.05)} |
| 7 | .btoggle{width:16px;height:16px;padding:0;border:none;background:transparent;color:#808080;display:inline-flex;align-items:center;justify-content:center} |
| 8 | .btoggle:disabled{cursor:not-allowed;color:#525252} |
| 9 | .bcaret{width:10px;height:10px;transition:transform .2s} |
| 10 | .bnode[aria-expanded="false"] .bcaret{transform:rotate(-90deg)} |
| 11 | .bnode[aria-expanded="false"] > ul{display:none} |
| 12 | .brow.disabled,.bnode[aria-disabled="true"] > .brow{color:#525252;cursor:not-allowed} |
| 13 | </style> |
| 14 | <ul class="bstree" role="tree" aria-label="Permissions"> |
| 15 | <li class="bnode" role="treeitem" aria-expanded="true"> |
| 16 | <div class="brow"> |
| 17 | <button class="btoggle" aria-label="Toggle organization"> |
| 18 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 19 | <path d="M6 9l6 6 6-6"/> |
| 20 | </svg> |
| 21 | </button> |
| 22 | <span> |
| 23 | Organization |
| 24 | </span> |
| 25 | </div> |
| 26 | <ul role="group"> |
| 27 | <li class="bnode" role="treeitem"> |
| 28 | <div class="brow"> |
| 29 | <span class="btoggle"> |
| 30 | </span> |
| 31 | <span> |
| 32 | Admins |
| 33 | </span> |
| 34 | </div> |
| 35 | </li> |
| 36 | <li class="bnode" role="treeitem" aria-disabled="true"> |
| 37 | <div class="brow disabled"> |
| 38 | <span class="btoggle"> |
| 39 | </span> |
| 40 | <span> |
| 41 | Guests (read-only) |
| 42 | </span> |
| 43 | </div> |
| 44 | </li> |
| 45 | </ul> |
| 46 | </li> |
| 47 | <li class="bnode" role="treeitem" aria-expanded="false" aria-disabled="true"> |
| 48 | <div class="brow disabled"> |
| 49 | <button class="btoggle" aria-label="Toggle legacy" disabled> |
| 50 | <svg class="bcaret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> |
| 51 | <path d="M6 9l6 6 6-6"/> |
| 52 | </svg> |
| 53 | </button> |
| 54 | <span> |
| 55 | Legacy Projects |
| 56 | </span> |
| 57 | </div> |
| 58 | <ul role="group"> |
| 59 | <li class="bnode" role="treeitem"> |
| 60 | <div class="brow disabled"> |
| 61 | <span class="btoggle"> |
| 62 | </span> |
| 63 | <span> |
| 64 | v0-archive |
| 65 | </span> |
| 66 | </div> |
| 67 | </li> |
| 68 | </ul> |
| 69 | </li> |
| 70 | </ul> |
| 71 | </div> |
Tree views are complex widgets. Follow the WAI-ARIA tree pattern so assistive technologies can announce levels, expansion, selection, and disabled states correctly.
- Wrap the root in <ul role="tree"> and each node in <li role="treeitem">.
- Set aria-expanded on branch nodes; omit it on leaf nodes.
- Use aria-selected for single- or multi-select trees.
- Use aria-disabled for non-interactive nodes instead of removing them.
- Give the tree an accessible name with aria-label or aria-labelledby.
- Implement arrow-key navigation: ArrowDown/Up, ArrowRight/Left, Home, End, Enter, and Space.
- Ensure focus indicators are visible against the dark background.
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.