Usage
Use the Tree component to display a hierarchical structure of items.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template>Items
Use the items prop as an array of objects with the following properties:
icon?: stringlabel?: stringtrailingIcon?: stringdefaultExpanded?: booleandisabled?: booleanslot?: stringchildren?: TreeItem[]onToggle?: (e: TreeItemToggleEvent<TreeItem>) => voidonSelect?: (e: TreeItemSelectEvent<TreeItem>) => voidclass?: anyui?: { item?: ClassNameValue, itemWithChildren?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLabel?: ClassNameValue, linkTrailing?: ClassNameValue, linkTrailingIcon?: ClassNameValue, listWithChildren?: ClassNameValue }
label prop as identifier if no get-key is provided. Ideally you should provide a get-key function prop to return a unique identifier. Alternatively, you can use the labelKey prop to specify which property to use as the unique identifier.<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template>Multiple
Use the multiple prop to allow multiple item selections.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree multiple :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree multiple :items="items" />
</template>Nested
Use the nested prop to control whether the Tree is rendered with nested structure or as a flat list. Defaults to true.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree :items="items" />
</template>nested is false, all items are rendered at the same level with indentation to indicate hierarchy. This is useful for virtualization or drag and drop functionality.Color
Use the color prop to change the color of the Tree.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree color="neutral" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree color="neutral" :items="items" />
</template>Size
Use the size prop to change the size of the Tree.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree size="xl" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree size="xl" :items="items" />
</template>Trailing Icon
Use the trailing-icon prop to customize the trailing Icon of a parent node. Defaults to i-lucide-chevron-down.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
trailingIcon: 'i-lucide-chevron-down',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree trailing-icon="i-lucide-arrow-down" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
trailingIcon: 'i-lucide-chevron-down',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree trailing-icon="i-lucide-arrow-down" :items="items" />
</template>Expanded Icon
Use the expanded-icon and collapsed-icon props to customize the icons of a parent node when it is expanded or collapsed. Defaults to i-lucide-folder-open and i-lucide-folder respectively.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" :items="items" />
</template>app.config.ts under ui.icons.folder and ui.icons.folderOpen keys.vite.config.ts under ui.icons.folder and ui.icons.folderOpen keys.Disabled
Use the disabled prop to prevent any user interaction with the Tree.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app',
icon: 'i-lucide-folder',
defaultExpanded: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree disabled :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui'
const items = ref<TreeItem[]>([
{
label: 'app',
icon: 'i-lucide-folder',
defaultExpanded: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<PTree disabled :items="items" />
</template>item.disabled.Examples
Control selected item(s)
You can control the selected item(s) by using the default-value prop or the v-model directive.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
];
const value = ref();
</script>
<template>
<PTree
v-model="value"
:items="items"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
];
const value = ref();
</script>
<template>
<PTree
v-model="value"
:items="items"
/>
</template>
get-key prop to change the function used to get the unique key from each item when a v-model or default-value is provided.If you want to prevent an item from being selected, you can use the item.onSelect() property or the global select event:
<script setup lang="ts">
import type { TreeItemSelectEvent } from 'akar';
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
onSelect: (e: Event) => {
e.preventDefault();
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
];
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault();
}
}
</script>
<template>
<PTree
:items="items"
@select="onSelect"
/>
</template>
Control expanded items
You can control the expanded items by using the default-expanded prop or the v-model directive.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui';
const items = [
{
label: 'app/',
id: 'app',
children: [
{
label: 'composables/',
id: 'app/composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
id: 'app/components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', id: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', id: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
] satisfies Array<TreeItem>;
const expanded = ref(['app', 'app/composables']);
</script>
<template>
<PTree
v-model:expanded="expanded"
:items="items"
:get-key="i => i.id"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from 'pohon-ui';
const items = [
{
label: 'app/',
id: 'app',
children: [
{
label: 'composables/',
id: 'app/composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
id: 'app/components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', id: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', id: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
] satisfies Array<TreeItem>;
const expanded = ref(['app', 'app/composables']);
</script>
<template>
<PTree
v-model:expanded="expanded"
:items="items"
:get-key="i => i.id"
/>
</template>
If you want to prevent an item from being expanded, you can use the item.onToggle() property or the global toggle event:
<script setup lang="ts">
import type { TreeItemToggleEvent } from 'akar';
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
onToggle: (e: Event) => {
e.preventDefault();
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
];
function onToggle(e: TreeItemToggleEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'keydown') {
e.preventDefault();
}
}
</script>
<template>
<PTree
:items="items"
@toggle="onToggle"
/>
</template>
With checkbox in items
You can use the item-leading slot to add a Checkbox to the items. Use the multiple, propagate-select and bubble-select props to enable multi-selection with parent-child relationship and the select and toggle events to control the selected and expanded state of the items.
- app/
- composables/
- components/
- Card.vue
- Button.vue
- app.vue
- nuxt.config.ts
<script setup lang="ts">
import type { TreeItemSelectEvent } from 'akar';
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts' },
{ label: 'useUser.ts' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue' },
{ label: 'Button.vue' },
],
},
],
},
{ label: 'app.vue' },
{ label: 'nuxt.config.ts' },
];
const value = ref<(typeof items)>([]);
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault();
}
}
</script>
<template>
<PTree
v-model="value"
:as="{ link: 'div' }"
:items="items"
multiple
propagate-select
bubble-select
@select="onSelect"
>
<template #item-leading="{ selected, indeterminate, handleSelect }">
<PCheckbox
:model-value="indeterminate ? 'indeterminate' : selected"
tabindex="-1"
@change="handleSelect"
@click.stop
/>
</template>
</PTree>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItemSelectEvent } from 'akar';
import type { TreeItem } from 'pohon-ui';
const items: Array<TreeItem> = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts' },
{ label: 'useUser.ts' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue' },
{ label: 'Button.vue' },
],
},
],
},
{ label: 'app.vue' },
{ label: 'nuxt.config.ts' },
];
const value = ref<(typeof items)>([]);
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault();
}
}
</script>
<template>
<PTree
v-model="value"
:as="{ link: 'div' }"
:items="items"
multiple
propagate-select
bubble-select
@select="onSelect"
>
<template #item-leading="{ selected, indeterminate, handleSelect }">
<PCheckbox
:model-value="indeterminate ? 'indeterminate' : selected"
tabindex="-1"
@change="handleSelect"
@click.stop
/>
</template>
</PTree>
</template>
as prop to change the items from button to div as the Checkbox is also rendered as a button.With drag and drop
Use the useSortable composable from @vueuse/integrations to enable drag and drop functionality on the Tree. This integration wraps Sortable.js to provide a seamless drag and drop experience.
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<Array<TreeItem>>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
])
function flatten(
items: Array<TreeItem>,
parent = items
): Array<{ item: TreeItem; parent: Array<TreeItem>; index: number }> {
return items.flatMap((item, index) => [
{ item, parent, index },
...(item.children?.length && item.defaultExpanded ? flatten(item.children, item.children) : [])
])
}
function moveItem(oldIndex: number, newIndex: number) {
if (oldIndex === newIndex) {
return
}
const flat = flatten(items.value)
const source = flat[oldIndex]
const target = flat[newIndex]
if (!source || !target) {
return
}
const [moved] = source.parent.splice(source.index, 1)
if (!moved) {
return
}
const updatedFlat = flatten(items.value)
const updatedTarget = updatedFlat.find(({ item }) => item === target.item)
if (!updatedTarget) {
return
}
const insertIndex = oldIndex < newIndex ? updatedTarget.index + 1 : updatedTarget.index
updatedTarget.parent.splice(insertIndex, 0, moved)
}
const tree = useTemplateRef<HTMLElement>('tree')
useSortable(tree, items, {
animation: 150,
ghostClass: 'opacity-50',
onUpdate: (e: any) => moveItem(e.oldIndex, e.newIndex)
})
</script>
<template>
<PTree ref="tree" :nested="false" :unmount-on-hide="false" :items="items" />
</template><script setup lang="ts">
import { shallowRef, useTemplateRef } from 'vue'
import type { TreeItem } from 'pohon-ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<Array<TreeItem>>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
])
function flatten(
items: Array<TreeItem>,
parent = items
): Array<{ item: TreeItem; parent: Array<TreeItem>; index: number }> {
return items.flatMap((item, index) => [
{ item, parent, index },
...(item.children?.length && item.defaultExpanded ? flatten(item.children, item.children) : [])
])
}
function moveItem(oldIndex: number, newIndex: number) {
if (oldIndex === newIndex) {
return
}
const flat = flatten(items.value)
const source = flat[oldIndex]
const target = flat[newIndex]
if (!source || !target) {
return
}
const [moved] = source.parent.splice(source.index, 1)
if (!moved) {
return
}
const updatedFlat = flatten(items.value)
const updatedTarget = updatedFlat.find(({ item }) => item === target.item)
if (!updatedTarget) {
return
}
const insertIndex = oldIndex < newIndex ? updatedTarget.index + 1 : updatedTarget.index
updatedTarget.parent.splice(insertIndex, 0, moved)
}
const tree = useTemplateRef<HTMLElement>('tree')
useSortable(tree, items, {
animation: 150,
ghostClass: 'opacity-50',
onUpdate: (e: any) => moveItem(e.oldIndex, e.newIndex)
})
</script>
<template>
<PTree ref="tree" :nested="false" :unmount-on-hide="false" :items="items" />
</template>nested prop to false to have a flat list of items so that the items can be dragged and dropped.With virtualization
Use the virtualize prop to enable virtualization for large lists as a boolean or an object with options like { estimateSize: 32, overscan: 12 }.
nested prop to false.<script setup lang="ts">
import type { TreeItem } from 'pohon-ui'
const items: Array<TreeItem> = Array(1000)
.fill(0)
.map((_, i) => ({
label: `Item ${i + 1}`,
children: [
{ label: `Child ${i + 1}-1`, icon: 'i-lucide-file' },
{ label: `Child ${i + 1}-2`, icon: 'i-lucide-file' }
]
}))
</script>
<template>
<PTree virtualize :items="items" class="h-80" />
</template>With custom slot
Use the slot property to customize a specific item.
You will have access to the following slots:
#{{ item.slot }}-wrapper#{{ item.slot }}#{{ item.slot }}-leading#{{ item.slot }}-label#{{ item.slot }}-trailing
<script setup lang="ts">
import type { TreeItem } from 'pohon-ui';
const items = [
{
label: 'app/',
slot: 'app' as const,
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' },
],
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' },
],
},
],
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
] satisfies Array<TreeItem>;
</script>
<template>
<PTree :items="items">
<template #app="{ item }">
<p class="font-bold italic">
{{ item.label }}
</p>
</template>
</PTree>
</template>
API
Props
| Prop | Default | Type |
|---|
Slots
| Slot | Type |
|---|
Emits
| Event | Type |
|---|
Theme
export default defineAppConfig({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: 'w-full',
listWithChildren: 'border-s border-border',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'group text-sm flex w-full select-none items-center relative focus-visible:outline-none focus:outline-none before:rounded-md before:content-empty before:inset-x-0 before:inset-y-px before:absolute before:-z-1 focus-visible:before:outline-3',
linkLeadingIcon: 'shrink-0 relative',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transition-transform-280 ease-out group-data-[expanded]:rotate-180 motion-reduce:transition-none'
},
variants: {
virtualize: {
true: {
root: 'overflow-y-auto'
}
},
color: {
primary: {
link: 'before:outline-primary/25'
},
secondary: {
link: 'before:outline-secondary/25'
},
success: {
link: 'before:outline-success/25'
},
info: {
link: 'before:outline-info/25'
},
warning: {
link: 'before:outline-warning/25'
},
error: {
link: 'before:outline-error/25'
},
neutral: {
link: 'before:outline-border-inverted/25'
}
},
size: {
xs: {
listWithChildren: 'ms-4',
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
listWithChildren: 'ms-4.5',
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
listWithChildren: 'ms-5',
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
listWithChildren: 'ms-5.5',
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
listWithChildren: 'ms-6',
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-background-elevated'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'color-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'color-text-highlighted'
}
},
{
selected: false,
disabled: false,
class: {
link: [
'hover:color-text-highlighted hover:before:bg-background-elevated/50',
'transition-colors before:transition-colors'
]
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from 'pohon-ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: 'w-full',
listWithChildren: 'border-s border-border',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'group text-sm flex w-full select-none items-center relative focus-visible:outline-none focus:outline-none before:rounded-md before:content-empty before:inset-x-0 before:inset-y-px before:absolute before:-z-1 focus-visible:before:outline-3',
linkLeadingIcon: 'shrink-0 relative',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transition-transform-280 ease-out group-data-[expanded]:rotate-180 motion-reduce:transition-none'
},
variants: {
virtualize: {
true: {
root: 'overflow-y-auto'
}
},
color: {
primary: {
link: 'before:outline-primary/25'
},
secondary: {
link: 'before:outline-secondary/25'
},
success: {
link: 'before:outline-success/25'
},
info: {
link: 'before:outline-info/25'
},
warning: {
link: 'before:outline-warning/25'
},
error: {
link: 'before:outline-error/25'
},
neutral: {
link: 'before:outline-border-inverted/25'
}
},
size: {
xs: {
listWithChildren: 'ms-4',
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
listWithChildren: 'ms-4.5',
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
listWithChildren: 'ms-5',
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
listWithChildren: 'ms-5.5',
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
listWithChildren: 'ms-6',
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-background-elevated'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'color-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'color-text-highlighted'
}
},
{
selected: false,
disabled: false,
class: {
link: [
'hover:color-text-highlighted hover:before:bg-background-elevated/50',
'transition-colors before:transition-colors'
]
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})
]
})