| name | n-select |
| description | A versatile select component for single and multiple selection with features like filtering, remote search, grouping, and virtual scrolling |
| author | jiaiyan |
| version | 1.0.0 |
n-select Component
The n-select component is a powerful dropdown selection component in Naive UI that supports single selection, multiple selection, searchable filtering, remote data loading, option grouping, and virtual scrolling for large datasets.
When to Use
Use n-select when you need to:
- Single Selection: Allow users to select one option from a dropdown list
- Multiple Selection: Enable users to select multiple options with tag display
- Searchable Dropdowns: Provide filtering capabilities for large option lists
- Remote Data Loading: Fetch options asynchronously from a server
- Grouped Options: Organize options into logical groups
- Tag Input: Create a tag input box for user-defined entries
Basic Usage
Single Select
<template>
<n-select v-model:value="selectedValue" :options="options" />
</template>
<script setup>
import { ref } from 'vue'
const selectedValue = ref(null)
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' }
]
</script>
Multiple Select
<template>
<n-select v-model:value="selectedValues" multiple :options="options" />
</template>
<script setup>
import { ref } from 'vue'
const selectedValues = ref([])
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' }
]
</script>
Filterable Select
<template>
<n-select
v-model:value="selectedValue"
filterable
placeholder="Search and select..."
:options="options"
/>
</template>
Disabled State
<template>
<n-select v-model:value="selectedValue" disabled :options="options" />
</template>
Different Sizes
<template>
<n-space vertical>
<n-select v-model:value="value" size="tiny" :options="options" />
<n-select v-model:value="value" size="small" :options="options" />
<n-select v-model:value="value" size="medium" :options="options" />
<n-select v-model:value="value" size="large" :options="options" />
</n-space>
</template>
API Reference
Props
| Name | Type | Default | Description |
|---|
value / v-model:value | Array<string | number> | string | number | null | undefined | The selected value(s). Use array for multiple selection. |
options | Array<SelectOption | SelectGroupOption> | [] | The options to display in the dropdown. |
multiple | boolean | false | Whether to allow multiple selections. |
filterable | boolean | false | Whether options can be filtered by typing. |
clearable | boolean | false | Whether the selection can be cleared. |
disabled | boolean | false | Whether the select is disabled. |
placeholder | string | 'Please Select' | Placeholder text when no value is selected. |
loading | boolean | false | Whether to show a loading indicator. |
remote | boolean | false | Whether to use remote search (disables local filter). |
size | 'tiny' | 'small' | 'medium' | 'large' | 'medium' | Size of the select input. |
menu-size | 'tiny' | 'small' | 'medium' | 'large' | 'medium' | Size of the dropdown menu. |
max-tag-count | |
Option Properties
SelectOption
| Name | Type | Description |
|---|
value | string | number | Unique value for the option (required). |
label | string | ((option: SelectOption, selected: boolean) => VNodeChild) | Display label for the option. |
disabled | boolean | Whether the option is disabled. |
class | string | CSS class for the option element. |
style | string | CSSProperties | Inline styles for the option element. |
render | (info: { node: VNode, option: SelectOption, selected: boolean }) => VNodeChild | Custom render function for the option. |
SelectGroupOption
| Name | Type | Description |
|---|
type | 'group' | Must be set to 'group'. |
label | string | ((option: SelectGroupOption) => VNodeChild) | Label for the group header. |
key | string | number | Unique key for the group. |
children | Array<SelectOption> | Array of options in the group. |
render | (info: { node: VNode, option: SelectOption, selected: boolean }) => VNodeChild | Custom render function for the group. |
Events
| Name | Parameters | Description |
|---|
update:value | (value: Array | string | number | null, option: SelectBaseOption | null | SelectBaseOption[]) => void | Emitted when the selected value changes. |
update:show | (show: boolean) => void | Emitted when the dropdown visibility changes. |
focus | () => void | Emitted when the select input gains focus. |
blur | () => void | Emitted when the select input loses focus. |
clear | () => void | Emitted when the selection is cleared. |
scroll | (e: ScrollEvent) => void | Emitted when the dropdown menu is scrolled. |
search | (value: string) => void | Emitted when a search is performed (useful for remote search). |
Slots
| Name | Parameters | Description |
|---|
empty | () | Content to show when there are no options. |
action | () | Content at the bottom of the dropdown menu. |
header | () | Content at the top of the dropdown menu. |
arrow | () | Custom dropdown arrow icon. |
Methods
| Name | Type | Description |
|---|
focus | () => void | Focus the select input. |
blur | () => void | Blur the select input. |
focusInput | () => void | Focus the internal input element. |
blurInput | () => void | Blur the internal input element. |
Common Patterns
Remote Search
<template>
<n-select
v-model:value="selectedValue"
filterable
remote
:options="options"
:loading="loading"
placeholder="Search..."
@search="handleSearch"
/>
</template>
<script setup>
import { ref } from 'vue'
const selectedValue = ref(null)
const options = ref([])
const loading = ref(false)
const handleSearch = async (query) => {
if (!query) return
loading.value = true
try {
const response = await fetch(`/api/search?q=${query}`)
const data = await response.json()
options.value = data.map(item => ({
label: item.name,
value: item.id
}))
} finally {
loading.value = false
}
}
</script>
Group Options
<template>
<n-select v-model:value="selectedValue" :options="groupedOptions" />
</template>
<script setup>
import { ref } from 'vue'
const selectedValue = ref(null)
const groupedOptions = [
{
type: 'group',
label: 'Group A',
key: 'group-a',
children: [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' }
]
},
{
type: 'group',
label: 'Group B',
key: 'group-b',
children: [
{ label: 'Option 3', value: '3' },
{ label: 'Option 4', value: '4' }
]
}
]
</script>
Tag Input Mode
<template>
<n-select
v-model:value="tags"
filterable
multiple
tag
:show-arrow="false"
:show="false"
placeholder="Type and press enter to add tags"
/>
</template>
<script setup>
import { ref } from 'vue'
const tags = ref([])
</script>
Custom Field Names
<template>
<n-select
v-model:value="selectedValue"
label-field="name"
value-field="id"
children-field="items"
:options="customOptions"
/>
</template>
<script setup>
import { ref } from 'vue'
const selectedValue = ref(null)
const customOptions = [
{ name: 'Item 1', id: 1 },
{ name: 'Item 2', id: 2 }
]
</script>
Infinite Scroll Loading
<template>
<n-select
:options="options"
:reset-menu-on-options-change="false"
@scroll="handleScroll"
/>
</template>
<script setup>
import { ref } from 'vue'
const options = ref([])
const page = ref(1)
const loadMore = async () => {
const newOptions = await fetchOptions(page.value)
options.value = [...options.value, ...newOptions]
page.value++
}
const handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target
if (scrollHeight - scrollTop <= clientHeight + 50) {
loadMore()
}
}
loadMore()
</script>
Custom Option Rendering
<template>
<n-select
:options="options"
:render-label="renderLabel"
/>
</template>
<script setup>
import { h } from 'vue'
const renderLabel = (option, selected) => {
return h('div', { style: { display: 'flex', alignItems: 'center' } }, [
h('span', { style: { marginRight: '8px' } }, option.label),
selected && h('span', { style: { color: 'green' } }, '✓')
])
}
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' }
]
</script>
Best Practices
Performance
-
Use Virtual Scroll: For large datasets (100+ options), keep virtual-scroll enabled (default) for optimal performance.
-
Limit Options: When possible, limit the number of options displayed at once, especially for remote search scenarios.
-
Debounce Remote Search: Add debouncing to remote search handlers to reduce API calls:
<script setup>
import { ref } from 'vue'
import { useDebounceFn } from '@vueuse/core'
const handleSearch = useDebounceFn(async (query) => {
// API call here
}, 300)
</script>
User Experience
-
Provide Clear Feedback: Use loading state during async operations to inform users that data is being fetched.
-
Use Meaningful Placeholders: Provide clear placeholder text that guides users on what to select or search.
-
Consider Max Tag Count: In multiple selection mode, use max-tag-count="responsive" or a specific number to prevent layout issues when many items are selected.
-
Enable Clearable: Allow users to easily clear their selection, especially for single-select scenarios.
Data Handling
-
Unique Values: Ensure each option has a unique value property to prevent unexpected behavior.
-
Handle Fallback Options: Decide whether to show fallback options for values without matching options. Set fallback-option="false" to automatically clear invalid values.
-
Custom Field Mapping: Use label-field, value-field, and children-field props when working with backend data that doesn't match the default field names.
Accessibility
-
Keyboard Navigation: Keep keyboard enabled (default) to allow users to navigate options using arrow keys.
-
Focus Management: Use the focus and blur methods for programmatic control when needed.
Form Integration
-
Validation Status: Use the status prop to show validation feedback (success, warning, error).
-
Disabled State: Use disabled prop to prevent selection when the field should be read-only.
-
Default Values: Set appropriate default-value for uncontrolled usage scenarios.