Menus, category trees and threaded comments are trees: each row may hold a list of rows like itself. {tree} renders its body for every row, and inside that body {recurse} renders the same body again for the current row's children. {branch} renders only when the row has children - the place for the <ul> around the next level.

PHP
fragments/trees_1.php
HTML
fragments/trees_1.pad
Result
<?php

$menu
= [
[
'title' => 'Home' ],
[
'title' => 'Shop', 'items' => [
[
'title' => 'Shoes', 'items' => [ [ 'title' => 'Boots' ], [ 'title' => 'Sandals' ] ] ],
[
'title' => 'Hats' ] ] ],
[
'title' => 'About' ]
];

?>
<ul>
{tree 'menu', children='items'}
<li>{$title}
{branch}<ul>{recurse}</ul>{/branch}
</li>
{/tree}
</ul>
  • Home
  • Shop
    • Shoes
      • Boots
      • Sandals
    • Hats
  • About

The rows are named by the first parameter or by data= - a {data} block, an array of the page, a _data/ file. children names the field that holds a row's children, children when not given.

Depth and the other properties

depth@tree is 1 for the tree's own rows and one more for every level down - for indentation. The other properties, such as current@tree and count@tree, speak of the list being rendered. The @else@ part of {branch} renders for a leaf, and the handling options sort, where and reverse on the tree apply to every list, the children included.

PHP
fragments/trees_2.php
HTML
fragments/trees_2.pad
Result
<?php

$menu
= [
[
'title' => 'Home' ],
[
'title' => 'Shop', 'items' => [
[
'title' => 'Shoes', 'items' => [ [ 'title' => 'Boots' ], [ 'title' => 'Sandals' ] ] ],
[
'title' => 'Hats' ] ] ],
[
'title' => 'About' ]
];

?>
{tree 'menu', children='items', sort='title'}
<p style="margin: 0 0 0 {echo depth@tree | * 20}px">
{branch}&#9662;@else@&middot;{/branch}
{$title} <small>({current@tree} of {count@tree})</small>
</p>
{branch}{recurse}{/branch}
{/tree}

· About (1 of 3)

· Home (2 of 3)

▾ Shop (3 of 3)

· Hats (1 of 2)

▾ Shoes (2 of 2)

· Boots (1 of 2)

· Sandals (2 of 2)