File Tree
A collapsible file/folder explorer with expandable directories and file/folder icons, under a simple "Explorer" header. Fully configurable from the Settings tab: add, remove, and re-parent items without touching PA formulas. Inspired by the shadcn/ui CollapsibleFileTree component.
How to Use
- On the Settings tab, edit the "Tree Items" list: each row is Name, Is Folder (toggle), and Parent (pick another item from the dropdown to nest under it, or "Top level"). Items automatically indent under their parent right in the list as you pick, and the dropdown only offers valid parents (it hides the item itself and anything already nested under it, so you can't create a loop). The Preview tab and the copyable YAML update together.
- Nesting is limited to 3 levels (matches Power Fx having no recursion; see the integration note below). An item whose parent chain goes deeper than 3 levels is clamped to the deepest supported level rather than erroring.
- Paste the YAML into Power Apps Studio (Ctrl+V). One GroupContainer (grpFileTree) is added with a static "Explorer" header label and a single templated Gallery (galFileTree) rendering the tree.
- Expand/collapse state lives in a collection, colFileTreeExpanded, storing the Id of every expanded folder: starts empty (all collapsed), no OnVisible needed.
- galFileTree.Height is Min(Self.AllItemsCount * 28, 700): it shrinks to fit only a few visible rows, but caps at 700px and scrolls internally once enough folders are expanded, instead of growing indefinitely. Adjust the 700 to change the cap.
- To rename the header label, edit lblFileTreeHeader.Text.
Problem Solved
Provides a ready-made hierarchical file/folder browser (the kind of UI Power Apps has no native control for) with proper expand/collapse state, folder vs. file iconography, and per-level indentation, built entirely from a single Gallery instead of hand-nesting containers per level. Unlike a typical PA component, the tree structure itself is editable from the website, no need to hand-edit a Table() literal in Studio.
Integration
The website computes Id/Level/ParentId/GrandparentId from your Name/Is Folder/Parent list and bakes them into the copied YAML as a literal Table(). Power Fx has no recursion, so only 2 ancestor hops (parent + grandparent) are checked, capping real nesting at 3 levels. Read which folders are open via colFileTreeExpanded (collection of {Id}). Swap the Table() in galFileTree.Items for a real data source later if you want it driven by live data (e.g. SharePoint/OneDrive) instead of a fixed list.
YAML Code
- grpFileTree:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
BorderColor: =RGBA(226, 232, 240, 1)
BorderThickness: =1
DropShadow: =DropShadow.None
Fill: =RGBA(255, 255, 255, 1)
Height: =headerFileTree.Height + galFileTree.Height
LayoutDirection: =LayoutDirection.Vertical
RadiusBottomLeft: =12
RadiusBottomRight: =12
RadiusTopLeft: =12
RadiusTopRight: =12
Width: =260
X: =20
Y: =20
Children:
- headerFileTree:
Control: GroupContainer@1.5.0
Variant: AutoLayout
Properties:
DropShadow: =DropShadow.None
Fill: =RGBA(255, 255, 255, 1)
FillPortions: =0
Height: =36
LayoutDirection: =LayoutDirection.Horizontal
PaddingBottom: =6
PaddingLeft: =12
PaddingTop: =12
RadiusBottomLeft: =0
RadiusBottomRight: =0
RadiusTopLeft: =12
RadiusTopRight: =12
Children:
- lblFileTreeHeader:
Control: Label@2.5.1
Properties:
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
Height: =18
Size: =10
Text: ="EXPLORER"
Width: =220
- galFileTree:
Control: Gallery@2.15.0
Variant: Vertical
Properties:
Fill: =RGBA(255, 255, 255, 1)
FillPortions: =0
Height: =Min(Self.AllItemsCount * 28, 700)
Items: |-
=Filter(Table({Id: 1, Name: "components", Level: 0, ParentId: 0, GrandparentId: 0, IsFolder: true}, {Id: 2, Name: "ui", Level: 1, ParentId: 1, GrandparentId: 0, IsFolder: true}, {Id: 3, Name: "button.tsx", Level: 2, ParentId: 2, GrandparentId: 1, IsFolder: false}, {Id: 4, Name: "card.tsx", Level: 2, ParentId: 2, GrandparentId: 1, IsFolder: false}, {Id: 5, Name: "login-form.tsx", Level: 1, ParentId: 1, GrandparentId: 0, IsFolder: false}, {Id: 6, Name: "lib", Level: 0, ParentId: 0, GrandparentId: 0, IsFolder: true}, {Id: 7, Name: "utils.ts", Level: 1, ParentId: 6, GrandparentId: 0, IsFolder: false}, {Id: 8, Name: "app.tsx", Level: 0, ParentId: 0, GrandparentId: 0, IsFolder: false}, {Id: 9, Name: "package.json", Level: 0, ParentId: 0, GrandparentId: 0, IsFolder: false}, {Id: 10, Name: "README.md", Level: 0, ParentId: 0, GrandparentId: 0, IsFolder: false}), Level = 0 || (Level = 1 && CountRows(Filter(colFileTreeExpanded, Id = ParentId)) > 0) || (Level = 2 && CountRows(Filter(colFileTreeExpanded, Id = ParentId)) > 0 && CountRows(Filter(colFileTreeExpanded, Id = GrandparentId)) > 0))
LayoutMinHeight: =0
LayoutMinWidth: =120
ShowScrollbar: =false
TemplatePadding: =0
TemplateSize: =28
Children:
- imgFileTreeChevron:
Control: Image@2.2.3
Properties:
Height: =14
Image: =If(ThisItem.IsFolder, If(CountRows(Filter(colFileTreeExpanded, Id = ThisItem.Id)) > 0, "data:image/svg+xml;utf8, " & EncodeUrl(Substitute("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'></path></svg>", "currentColor", "RGBA(100,116,139,1)")), "data:image/svg+xml;utf8, " & EncodeUrl(Substitute("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m9 18 6-6-6-6'></path></svg>", "currentColor", "RGBA(100,116,139,1)"))), Blank())
Visible: =ThisItem.IsFolder
Width: =14
X: =12 + ThisItem.Level * 16
Y: =7
- imgFileTreeIcon:
Control: Image@2.2.3
Properties:
Height: =14
Image: =If(ThisItem.IsFolder, "data:image/svg+xml;utf8, " & EncodeUrl(Substitute("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'></path></svg>", "currentColor", "RGBA(100,116,139,1)")), "data:image/svg+xml;utf8, " & EncodeUrl(Substitute("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'></path><path d='M14 2v4a2 2 0 0 0 2 2h4'></path></svg>", "currentColor", "RGBA(148,163,184,1)")))
Width: =14
X: =32 + ThisItem.Level * 16
Y: =7
- lblFileTreeName:
Control: Label@2.5.1
Properties:
Color: =If(ThisItem.IsFolder, RGBA(15, 23, 42, 1), RGBA(51, 65, 85, 1))
Font: =Font.'Open Sans'
FontWeight: =If(ThisItem.IsFolder, FontWeight.Semibold, FontWeight.Normal)
Height: =28
Size: =11
Text: =ThisItem.Name
Width: =Parent.TemplateWidth - (52 + ThisItem.Level * 16)
X: =52 + ThisItem.Level * 16
- btnFileTreeToggle:
Control: Button@0.0.45
Properties:
Appearance: ='ButtonCanvas.Appearance'.Transparent
Height: =28
OnSelect: |-
=If(ThisItem.IsFolder, If(CountRows(Filter(colFileTreeExpanded, Id = ThisItem.Id)) > 0, Remove(colFileTreeExpanded, LookUp(colFileTreeExpanded, Id = ThisItem.Id)), Collect(colFileTreeExpanded, {Id: ThisItem.Id})))
Text: =""
Width: =Parent.TemplateWidthImplementation
Copies the customized component code directly to your clipboard. Use Ctrl+V in Power Apps Studio.
Something not pasting right? Troubleshooting guide