| name | n-transfer |
| description | A dual-list transfer component for moving items between source and target lists with filtering, virtual scrolling, and custom rendering capabilities |
| author | jiaiyan |
| version | 1.0.0 |
n-transfer Component
The n-transfer component is a dual-list selection interface that allows users to move items between a source list and a target list. It's ideal for scenarios where users need to select multiple items from a larger pool, such as assigning permissions, selecting team members, or configuring features.
When to Use
Use n-transfer when you need to:
- Multi-Item Selection: Allow users to select multiple items from a large pool of options
- Permission Assignment: Assign permissions or roles to users with clear visual feedback
- Team Member Selection: Select multiple team members for projects or tasks
- Feature Configuration: Enable/disable features with a clear before/after view
- Large Datasets: Handle large datasets efficiently with virtual scrolling
Basic Usage
Basic Transfer
<template>
<n-transfer v-model:value="value" :options="options" />
</template>
<script setup>
import { ref } from 'vue'
const value = ref([])
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
{ label: 'Option 4', value: '4' },
{ label: 'Option 5', value: '5' }
]
</script>
Large Data with Virtual Scroll
<template>
<n-transfer v-model:value="value" :options="options" virtual-scroll />
</template>
<script setup>
import { ref } from 'vue'
const value = ref([])
const options = Array.from({ length: 1000 }, (_, i) => ({
label: `Option ${i + 1}`,
value: String(i + 1)
}))
</script>
Filterable Transfer
<template>
<n-space vertical>
<n-transfer
v-model:value="value1"
virtual-scroll
:options="options"
source-filterable
/>
<n-transfer
v-model:value="value2"
virtual-scroll
:options="options"
target-filterable
/>
</n-space>
</template>
<script setup>
import { ref } from 'vue'
const value1 = ref([])
const value2 = ref([])
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' }
]
</script>
Custom Labels
<template>
<n-transfer
v-model:value="value"
:options="options"
:render-target-label="renderLabel"
/>
</template>
<script setup>
import { ref, h } from 'vue'
const value = ref([])
const options = [
{ label: 'User A', value: 'a' },
{ label: 'User B', value: 'b' }
]
const renderLabel = ({ option }) => {
return h('div', { style: { display: 'flex', alignItems: 'center' } }, [
h('span', { style: { marginRight: '8px' } }, '👤'),
h('span', option.label)
])
}
</script>
Custom Source List
<template>
<n-transfer
v-model:value="value"
:options="options"
:render-source-list="renderSourceList"
source-filterable
/>
</template>
API Reference
Props
| Name | Type | Default | Description |
|---|
value / v-model:value | Array<string | number> | null | undefined | Selected values in the target list. |
default-value | Array<string | number> | null | null | Default uncontrolled value. |
options | TransferOption[] | [] | Options for the transfer list. |
disabled | boolean | false | Whether the transfer is disabled. |
virtual-scroll | boolean | false | Enable virtual scrolling for large datasets. |
source-filterable | boolean | false | Whether the source list can be filtered. |
target-filterable | boolean | false | Whether the target list can be filtered. |
source-filter-placeholder | string | undefined | Placeholder for the source search box. |
target-filter-placeholder | string | undefined | Placeholder for the target search box. |
source-title | string | (() => VNodeChild) | undefined | Title for the source panel. |
target-title | string | (() => VNodeChild) | undefined | Title for the target panel. |
size | 'small' | 'medium' | 'large' |
TransferOption Type
| Property | Type | Description |
|---|
label | string | Display label for the option. |
value | string | number | Unique value for the option. |
disabled | boolean | Whether the option is disabled. |
Events
| Name | Parameters | Description |
|---|
update:value | (value: Array<string | number>) => void | Emitted when the selected values change. |
Common Patterns
Permission Assignment
<template>
<n-transfer
v-model:value="selectedPermissions"
:options="permissions"
source-title="Available Permissions"
target-title="Assigned Permissions"
source-filterable
target-filterable
/>
</template>
<script setup>
import { ref } from 'vue'
const selectedPermissions = ref([])
const permissions = [
{ label: 'Read Users', value: 'users:read' },
{ label: 'Write Users', value: 'users:write' },
{ label: 'Delete Users', value: 'users:delete' },
{ label: 'Read Posts', value: 'posts:read' },
{ label: 'Write Posts', value: 'posts:write' }
]
</script>
Team Member Selection
<template>
<n-transfer
v-model:value="selectedMembers"
:options="members"
:render-source-label="renderMemberLabel"
:render-target-label="renderMemberLabel"
source-title="All Members"
target-title="Project Team"
source-filterable
virtual-scroll
/>
</template>
<script setup>
import { ref, h } from 'vue'
const selectedMembers = ref([])
const members = [
{ label: 'John Doe', value: 'john', disabled: false },
{ label: 'Jane Smith', value: 'jane', disabled: false },
{ label: 'Bob Wilson', value: 'bob', disabled: true }
]
const renderMemberLabel = ({ option }) => {
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
h('span', option.disabled ? '🚫' : '👤'),
h('span', option.label),
option.disabled && h('span', { style: { color: '#999' } }, '(Unavailable)')
])
}
</script>
Custom Filter Function
<template>
<n-transfer
v-model:value="value"
:options="options"
:filter="filterMethod"
source-filterable
target-filterable
/>
</template>
<script setup>
import { ref } from 'vue'
const value = ref([])
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' }
]
const filterMethod = (pattern, option, from) => {
return option.label.toLowerCase().includes(pattern.toLowerCase())
}
</script>
With Custom Titles
<template>
<n-transfer
v-model:value="value"
:options="options"
:source-title="() => h('span', { style: { color: '#18a058' } }, 'Available')"
:target-title="() => h('span', { style: { color: '#2080f0' } }, 'Selected')"
/>
</template>
<script setup>
import { ref, h } from 'vue'
const value = ref([])
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' }
]
</script>
Best Practices
Performance
-
Use Virtual Scroll: Always enable virtual-scroll when dealing with more than 100 options for optimal performance.
-
Debounce Filter: For custom filter functions with complex logic, consider debouncing the filter operation.
-
Lazy Loading: For extremely large datasets, consider implementing custom list rendering with lazy loading.
User Experience
-
Clear Titles: Provide meaningful source-title and target-title to help users understand the context.
-
Enable Filtering: Enable source-filterable for lists with more than 20 items to help users find options quickly.
-
Show Selected State: Keep show-selected enabled (default) so users can see which items are already selected in the source list.
-
Provide Feedback: Use appropriate clear-text and select-all-text for better localization support.
Data Handling
-
Unique Values: Ensure each option has a unique value property to prevent unexpected behavior.
-
Disabled Options: Use the disabled property on options to indicate unavailable items rather than removing them from the list.
-
Consistent Data Structure: Maintain a consistent data structure for options throughout your application.
Accessibility
-
Keyboard Navigation: The transfer component supports keyboard navigation by default. Ensure your custom renderers don't break this functionality.
-
Clear Labels: Provide clear and descriptive labels for each option.
-
Visual Feedback: Use custom renderers to provide additional visual feedback for different option states.