Skip to the page
TACTILE UI

Kit part · File trees

Drawer index

A file tree as a card-index cabinet. Folders are drawers, each with a label holder and a cup pull; open one and it slides out with its files standing in it as index cards, each showing its top edge over the card behind. A folder inside a drawer is a divider with a tab. It is a real tree for the keyboard and for a screen reader, drawn from data by one function.

Element
drawerIndexHTML()
Needs
kit.css + kit.mjs
Rules
drawer-index.rules.md · 6
Finishes
Aluminium · Graphite · Porcelain · Black
Volumes
Tactile · Quiet
On this page
  1. Variants
  2. Every state, at once
  3. API
  4. Markup
  5. Accessibility
  6. How it’s built
  7. Rules
  8. Related parts
01

Variants

Every drawer shut
  • releases3 files
    • 4.1.md8 KB
    • 4.2.md9 KB
    • 4.2.1.md2 KB
A drawer out, a card taken out
  • specs7 files
    • audit2 files
      • retention.md4 KB
      • export.md6 KB
    • glossary.md2 KB
Dividers, one open
  • handover-notes2 files
    • night-rota-handover-2026-09-28-ada-okafor.md3 KB
    • night-rota-handover-2026-09-29-jonathan-adeyemi-whitcombe.md5 KB
Long names are cut short
02

Every state, at once

Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.

Aluminium

PartRestFocusOpen
Drawer
  • keys1 file
    • lode-prod-32 KB
Card
  • keys2 files
    • lode-prod-2
    • lode-prod-3
  • keys2 files
    • lode-prod-2
    • lode-prod-3
  • keys2 files
    • lode-prod-2
    • lode-prod-3
Divider
  • specs1 file
  • specs1 file
  • specs1 file
    • audit1 file
      • export.md

Graphite

PartRestFocusOpen
Drawer
  • keys1 file
    • lode-prod-32 KB
Card
  • keys2 files
    • lode-prod-2
    • lode-prod-3
  • keys2 files
    • lode-prod-2
    • lode-prod-3
  • keys2 files
    • lode-prod-2
    • lode-prod-3
Divider
  • specs1 file
  • specs1 file
  • specs1 file
    • audit1 file
      • export.md
03

API

NameKindValuesWhat it does
drawerIndexHTML(tree, options)functionkit.mjsThe cabinet as markup, from data. tree: [{ name, meta, open, children }]; a node with children is a folder.
options.label · options.chosenoptionstring · a pathThe tree’s name, and the path of the card that starts taken out: its drawer and dividers start open and it holds the tab stop.
node.metaoptionstringA few characters at the end of a card, or beside a folder’s name. A folder without one says how many files it holds.
.tui-drawer-indexclassthe carcassThe cabinet, with ul[role="tree"].__cabinet inside it.
.__drawer · __front · __holder · __pullclasslevel 1A drawer: a treeitem with aria-expanded, its front, the label holder and the cup pull.
.__divider · __divide · __tabclasslevel 2 and deeperA folder in a drawer: pressboard with a tab. --tab (0, 1 or 2) says where along the row its tab is cut.
.__cardclassaria-selectedA file. The one taken out stands proud of the others, its lamp lit.
.__body · __groupclassrole="group"What slides out: the drawer’s box, or the cards behind a divider.
--di-cardcustom propertylengthHow much of each card shows over the one behind it: 34px.
tui-openeventdetail.path · name · folderWhen a card is taken out (folder: false) or a drawer or a divider is opened (folder: true). Bubbles from .tui-drawer-index. Needs kit.mjs.
Arrows · Home · End · Enter · Space · * · letterskeyboardon an itemThe tree pattern’s keys. Right opens a folder, then goes in; Left closes it, then goes out; * opens every folder beside this one; typing jumps to a name.
drawerIndexCount · drawerIndexMeta · drawerIndexTab · drawerIndexAncestors · drawerIndexPathfunctionkit.mjsThe rules, pure: how many files a folder holds, what its label says, where a tab is cut, the folders on the way to a path, and an item’s path.
04

Markup

<div class="tui-drawer-index">
  <ul class="tui-drawer-index__cabinet" role="tree" aria-label="Lode repository">
    <li class="tui-drawer-index__drawer" role="treeitem" aria-level="1" aria-expanded="true"
        aria-label="runbooks" tabindex="-1" data-name="runbooks">
      <span class="tui-drawer-index__front">
        <span class="tui-drawer-index__holder">
          <span class="tui-drawer-index__name">runbooks</span>
          <span class="tui-drawer-index__meta">7 files</span>
        </span>
        <span class="tui-drawer-index__pull" aria-hidden="true"></span>
      </span>
      <div class="tui-drawer-index__body">
        <ul class="tui-drawer-index__group" role="group">
          <li class="tui-drawer-index__card" role="treeitem" aria-level="2" aria-selected="true"
              tabindex="0" data-name="canary-rollback.md">
            <span class="tui-drawer-index__name">canary-rollback.md</span>
            <span class="tui-drawer-index__meta">6 KB</span>
          </li>
          <li class="tui-drawer-index__divider" role="treeitem" aria-level="2" aria-expanded="false"
              aria-label="regions" tabindex="-1" data-name="regions" style="--tab:0">…</li>
        </ul>
      </div>
    </li>
  </ul>
</div>
import { drawerIndexHTML } from '/kit/kit.mjs';   // also wires every drawer index on the page

box.innerHTML = drawerIndexHTML([
  { name: 'runbooks', children: [
    { name: 'canary-rollback.md', meta: '6 KB' },
    { name: 'regions', children: [{ name: 'eu-west.md' }, { name: 'us-east.md' }] },
  ] },
  { name: 'releases', children: [{ name: '4.2.md', meta: '9 KB' }] },
], { label: 'Lode repository', chosen: 'runbooks/canary-rollback.md' });

box.addEventListener('tui-open', (e) => {
  e.detail.path;     // 'runbooks/regions/eu-west.md'
  e.detail.folder;   // false for a card, true for a drawer or a divider
});

Needs kit.css and kit.mjs. How to install the kit.

05

Accessibility

  • It is a WAI-ARIA tree: a list with role="tree", every drawer, divider and card a treeitem with its level, and every folder with aria-expanded. One item is in the tab order and the arrow keys move from there.
  • The keys are the pattern’s own: Up and Down; Right to open and then go in; Left to close and then go out; Home and End; * for every folder beside this one; and typing to jump to a name.
  • A shut drawer is out of reach: its cards are hidden from assistive technology and from Tab, not only slid out of sight.
  • A folder is named by its own name, so a screen reader says “runbooks, expanded, level 1”, and not every name inside it.
  • The card taken out is aria-selected, and it also stands proud of the rest with its lamp lit. Colour is never the only sign.
  • Names and sizes are paper ink on paper, 13:1 and 6:1, in both finishes.
  • With reduced motion drawers open and shut at once, with no slide.
06

How it’s built

The markup is a nested list written by drawerIndexHTML(), roles and all, so it can come from a server and needs only the key and click handling that kit.mjs delegates once for the page. A drawer’s body is a one-row grid that goes from 0fr to 1fr, so it slides open without being measured, and visibility follows it shut so nothing inside can be reached. The cards come forward a few pixels as it opens. Each card is paper with a ruled line, pulled up under the one before it, and throws a short shadow upward onto it; every third card sits a pixel off square. The divider is pressboard, a mix of the paper and its ink, and --tab places its tab at the first, second or third cut along the row, as dividers are cut. The label holder is a paper card behind a pressed frame drawn with spread shadows, and the pull is a hood with its hollow under it. Type-ahead is the file tree’s own fileTreeMatch.

07

Rules

drawer-index.rules.mdIn the brain

For files people browse by name a section at a time: runbooks, specs, a repository.

6 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.

08
  • parts
  • calendar.mjs
  • carousel.mjs

File tree

Folders and files as a real tree, printed like a wiring diagram: traces with an elbow into every item, chevrons that turn, and the chosen file set in a recess with its lamp lit. Written as a plain nested list.

Tariff

Three rates a day.

Reserve

—

Accordion

Printed sections that open in place. Each head ends in a small square key printed +; open, the key latches below the plate and reads −.

  1. IntroPiano, room mics1:12
  2. LodeFull band4:05
  3. Night reelVocal and tape3:42

List

Rows in a rack: a mark, a title, a second line and a fact or a key at the end. Chosen rows sink into a recess with their check key latched; in a running order, a row lifts out of the rack on its skirt and slides past the others, from the keyboard or by its grip.

Docs
Finish
Volume