Usage
Items
Use the items
prop as an array of objects with the following properties:
value?: string
- Unique identifier for the itemlabel?: string
- Display text for the itemicon?: string
- Icon to display before the labelleadingIcon?: string
- Alternative icon before the labeltrailingIcon?: string
- Icon to display after the labeldisabled?: boolean
- Whether the item is disableddefaultOpen?: boolean
- Whether the item is expanded by defaultchildren?: TreeItem[]
- Nested itemsslot?: string
- Custom slot name for item content
value
prop as identifier, falling back to label
if value
is not provided. One of these must be provided for the component to work properly.- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template>
Color
Use the color
prop to change the color of the Tree.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree color="info" :items="items" />
</template>
Variant
Use the variant
prop to change the variant of the Tree.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree variant="link" color="info" :items="items" />
</template>
Size
Use the size
prop to change the size of the Tree.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template>
Multiple
Use the multiple
prop to allow multiple item selections.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree multiple :items="items" />
</template>
Disabled
Use the disabled
prop to disable the entire tree component. This will prevent any user interaction with the tree.
item.disabled
.- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultOpen: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'vscode-icons:file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'vscode-icons:file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'Button.vue',
icon: 'vscode-icons:file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'vscode-icons:file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'vscode-icons:file-type-nuxt'
}
])
</script>
<template>
<UTree disabled :items="items" />
</template>
Icon
Use icon
, leadingIcon
or trailingIcon
to set the default icons for nodes. For parent nodes, use parentIcon
, parentLeadingIcon
or parentTrailingIcon
.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = ref([
{
label: 'app',
defaultOpen: true,
children: [
{
label: 'composables',
children: [
{
label: 'useAuth.ts'
},
{
label: 'useUser.ts'
}
]
},
{
label: 'components',
children: [
{
label: 'Home',
children: [
{
label: 'Card.vue'
},
{
label: 'Button.vue'
}
]
}
]
}
]
},
{
label: 'app.vue'
},
{
label: 'nuxt.config.ts'
}
])
</script>
<template>
<UTree icon="i-lucide-dot" parent-icon="i-lucide-folder" :items="items" />
</template>
Examples
With rotating icon
You can animate the parent icon using the ui
prop.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = [
{
label: 'app',
defaultOpen: true,
children: [{
label: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'vscode-icons:file-type-typescript' },
{ label: 'useUser.ts', icon: 'vscode-icons:file-type-typescript' }
]
},
{
label: 'components',
children: [
{
label: 'Home',
children: [
{ label: 'Card.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'Button.vue', icon: 'vscode-icons:file-type-vue' }
]
}
]
}]
},
{ label: 'app.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'vscode-icons:file-type-nuxt' }
]
</script>
<template>
<UTree :items="items" parent-icon="lucide:chevron-right" :ui="{ itemLeadingIcon: 'group-data-[expanded]:rotate-90 transition-transform duration-200' }" />
</template>
With expand icon
You can change the icon based on the item state using slots.
- app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = [
{
label: 'app',
icon: 'lucide:folder',
defaultOpen: true,
children: [{
label: 'composables',
icon: 'lucide:folder',
children: [
{ label: 'useAuth.ts', icon: 'vscode-icons:file-type-typescript' },
{ label: 'useUser.ts', icon: 'vscode-icons:file-type-typescript' }
]
},
{
label: 'components',
icon: 'lucide:folder',
children: [
{
label: 'Home',
icon: 'lucide:folder',
children: [
{ label: 'Card.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'Button.vue', icon: 'vscode-icons:file-type-vue' }
]
}
]
}]
},
{ label: 'app.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'vscode-icons:file-type-nuxt' }
]
</script>
<template>
<UTree :items="items">
<template #item-leading="{ hasChildren, expanded }">
<UIcon v-if="hasChildren && expanded" name="lucide:folder-open" />
<UIcon v-else-if="hasChildren" name="lucide:folder" />
</template>
</UTree>
</template>
With custom slot
Use the item.slot
property to customize a specific item.
app
- composables
- components
- app.vue
- nuxt.config.ts
<script setup lang="ts">
const items = [
{
label: 'app',
slot: 'app',
defaultOpen: true,
children: [{
label: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'vscode-icons:file-type-typescript' },
{ label: 'useUser.ts', icon: 'vscode-icons:file-type-typescript' }
]
},
{
label: 'components',
children: [
{
label: 'Home',
children: [
{ label: 'Card.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'Button.vue', icon: 'vscode-icons:file-type-vue' }
]
}
]
}]
},
{ label: 'app.vue', icon: 'vscode-icons:file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'vscode-icons:file-type-nuxt' }
]
</script>
<template>
<UTree :items="items">
<template #app="{ item }">
<p class="italic font-bold">
{{ item.label }}
</p>
</template>
</UTree>
</template>
API
Props
Prop | Default | Type |
---|---|---|
as |
|
The element or component this component should render as. |
color |
|
|
size |
|
|
variant |
|
|
valueKey |
|
The key used to get the value from the item. |
labelKey |
|
The key used to get the label from the item. |
parentIcon |
The default leading icon displayed on parent nodes. | |
parentLeadingIcon |
The default leading icon displayed on parent nodes. | |
parentTrailingIcon |
The default trailing icon displayed on parent nodes. | |
icon |
The default leading icon displayed on all nodes. | |
leadingIcon |
The default leading icon displayed on all nodes. | |
trailingIcon |
The default trailing icon displayed on all nodes. | |
items |
| |
modelValue |
The controlled value of the Tree. Can be bind as | |
defaultValue |
The value of the Tree when initially rendered. Use when you do not need to control the state of the Tree. | |
multiple |
Whether multiple options can be selected or not. | |
disabled |
When | |
asChild |
Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
expanded |
The controlled value of the expanded item. Can be binded with with | |
selectionBehavior |
How multiple selection should behave in the collection. | |
defaultExpanded |
The value of the expanded tree when initially rendered. Use when you do not need to control the state of the expanded tree | |
getChildren |
This function is passed the index of each item and should return a list of children for that item | |
propagateSelect |
When | |
ui |
|
Slots
Slot | Type |
---|---|
item |
|
item-leading |
|
item-label |
|
item-trailing |
|
Emits
Event | Type |
---|---|
update:modelValue |
|
update:expanded |
|
Theme
export default defineAppConfig({
ui: {
tree: {
slots: {
root: 'relative',
item: [
'group relative w-full flex items-center select-none rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-disabled:bg-transparent data-disabled:text-(--ui-text-elevated) outline-none',
'transition-colors before:transition-colors'
],
itemLeadingIcon: 'shrink-0',
itemTrailing: 'ms-auto inline-flex',
itemTrailingIcon: 'shrink-0',
itemLabel: 'truncate'
},
variants: {
color: {
primary: 'focus-visible:ring-2 focus-visible:ring-(--ui-primary)',
secondary: 'focus-visible:ring-2 focus-visible:ring-(--ui-secondary)',
success: 'focus-visible:ring-2 focus-visible:ring-(--ui-success)',
info: 'focus-visible:ring-2 focus-visible:ring-(--ui-info)',
warning: 'focus-visible:ring-2 focus-visible:ring-(--ui-warning)',
error: 'focus-visible:ring-2 focus-visible:ring-(--ui-error)',
neutral: ''
},
variant: {
pill: {
item: 'hover:not-data-disabled:not-data-selected:bg-(--ui-bg-elevated) hover:not-data-disabled:not-data-selected:text-(--ui-text-highlighted)'
},
link: {
item: 'hover:not-data-disabled:not-data-selected:text-(--ui-text-highlighted)'
}
},
size: {
xs: {
root: 'text-xs',
item: 'p-0.75 gap-1',
itemLeadingIcon: 'size-3',
itemTrailingIcon: 'size-3'
},
sm: {
root: 'text-xs',
item: 'p-0.75 gap-1.5',
itemLeadingIcon: 'size-4.5',
itemTrailingIcon: 'size-4.5'
},
md: {
root: 'text-sm',
item: 'p-1 gap-1.5',
itemLeadingIcon: 'size-4',
itemTrailingIcon: 'size-4'
},
lg: {
root: 'text-sm',
item: 'p-1.25 gap-1.5',
itemLeadingIcon: 'size-4.5',
itemTrailingIcon: 'size-4.5'
},
xl: {
root: 'text-base',
item: 'p-1.25 gap-1.5',
itemLeadingIcon: 'size-5',
itemTrailingIcon: 'size-5'
}
}
},
compoundVariants: [
{
color: 'primary',
variant: 'pill',
class: {
item: 'data-selected:bg-(--ui-primary)/10 data-selected:text-(--ui-primary)'
}
},
{
color: 'neutral',
variant: 'pill',
class: {
item: 'data-selected:bg-(--ui-bg-elevated)/50 data-selected:text-(--ui-text-highlighted)'
}
},
{
color: 'primary',
variant: 'link',
class: {
item: 'data-selected:text-(--ui-primary)'
}
},
{
color: 'neutral',
variant: 'link',
class: {
item: 'data-selected:text-(--ui-text-highlighted)'
}
}
],
defaultVariants: {
color: 'primary',
variant: 'pill',
size: 'md'
}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
tree: {
slots: {
root: 'relative',
item: [
'group relative w-full flex items-center select-none rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-(--ui-text) data-disabled:bg-transparent data-disabled:text-(--ui-text-elevated) outline-none',
'transition-colors before:transition-colors'
],
itemLeadingIcon: 'shrink-0',
itemTrailing: 'ms-auto inline-flex',
itemTrailingIcon: 'shrink-0',
itemLabel: 'truncate'
},
variants: {
color: {
primary: 'focus-visible:ring-2 focus-visible:ring-(--ui-primary)',
secondary: 'focus-visible:ring-2 focus-visible:ring-(--ui-secondary)',
success: 'focus-visible:ring-2 focus-visible:ring-(--ui-success)',
info: 'focus-visible:ring-2 focus-visible:ring-(--ui-info)',
warning: 'focus-visible:ring-2 focus-visible:ring-(--ui-warning)',
error: 'focus-visible:ring-2 focus-visible:ring-(--ui-error)',
neutral: ''
},
variant: {
pill: {
item: 'hover:not-data-disabled:not-data-selected:bg-(--ui-bg-elevated) hover:not-data-disabled:not-data-selected:text-(--ui-text-highlighted)'
},
link: {
item: 'hover:not-data-disabled:not-data-selected:text-(--ui-text-highlighted)'
}
},
size: {
xs: {
root: 'text-xs',
item: 'p-0.75 gap-1',
itemLeadingIcon: 'size-3',
itemTrailingIcon: 'size-3'
},
sm: {
root: 'text-xs',
item: 'p-0.75 gap-1.5',
itemLeadingIcon: 'size-4.5',
itemTrailingIcon: 'size-4.5'
},
md: {
root: 'text-sm',
item: 'p-1 gap-1.5',
itemLeadingIcon: 'size-4',
itemTrailingIcon: 'size-4'
},
lg: {
root: 'text-sm',
item: 'p-1.25 gap-1.5',
itemLeadingIcon: 'size-4.5',
itemTrailingIcon: 'size-4.5'
},
xl: {
root: 'text-base',
item: 'p-1.25 gap-1.5',
itemLeadingIcon: 'size-5',
itemTrailingIcon: 'size-5'
}
}
},
compoundVariants: [
{
color: 'primary',
variant: 'pill',
class: {
item: 'data-selected:bg-(--ui-primary)/10 data-selected:text-(--ui-primary)'
}
},
{
color: 'neutral',
variant: 'pill',
class: {
item: 'data-selected:bg-(--ui-bg-elevated)/50 data-selected:text-(--ui-text-highlighted)'
}
},
{
color: 'primary',
variant: 'link',
class: {
item: 'data-selected:text-(--ui-primary)'
}
},
{
color: 'neutral',
variant: 'link',
class: {
item: 'data-selected:text-(--ui-text-highlighted)'
}
}
],
defaultVariants: {
color: 'primary',
variant: 'pill',
size: 'md'
}
}
}
})
]
})