Drill-down

Tweak the number of levels

The widget is unopinionated about depth — you decide how many levels of selection state to keep, and append a slide for each one. Below is the same budget data shown with 2 to 5 levels of drill-down.

Levels:
3-level drill-down
Budget
$266 tracked
2024
2 levels
Categories → Transactions. Flat month, two columns of state.
3 levels
Months → Categories → Transactions. Adds time as a top-level pivot.
4 levels
Years → Months → Categories → Transactions. Useful for archives.
5 levels
Years → Months → Categories → Transactions → Line items. Receipt-level detail.

How the code scales

Each level is one piece of state and one conditional slide. To add a deeper level, add another useState and another ...(selected ? [slide] : []) block. The widget renders whatever array you give it.

// 5-level drill-down — one state hook per level
const [yearId, setYearId] = useState<string | null>(null);
const [monthId, setMonthId] = useState<string | null>(null);
const [catId, setCatId] = useState<string | null>(null);
const [txId, setTxId] = useState<string | null>(null);
const [itemId, setItemId] = useState<string | null>(null);

const onPrev = () => {
  if (itemId) setItemId(null);
  else if (txId) setTxId(null);
  else if (catId) setCatId(null);
  else if (monthId) setMonthId(null);
  else if (yearId) setYearId(null);
};

const slides = [
  { key: "years",   content: <YearList onPick={setYearId} /> },
  ...(year   ? [{ key: "months",  onBack: () => setYearId(null),  content: <MonthList /> }] : []),
  ...(month  ? [{ key: "cats",    onBack: () => setMonthId(null), content: <CatList /> }] : []),
  ...(cat    ? [{ key: "txs",     onBack: () => setCatId(null),   content: <TxList /> }] : []),
  ...(tx     ? [{ key: "items",   onBack: () => setTxId(null),    content: <ItemList /> }] : []),
  ...(item   ? [{ key: "detail",  size: "2/3", onBack: () => setItemId(null), content: <Detail /> }] : []),
];

<MultiWidget
  slides={slides}
  hasSelection={yearId !== null}
  onPrev={onPrev}
/>

Tip: clear the deeper state when a parent changes (e.g. picking a new month should reset catId, txId, itemId) so the user never sees stale child slides.