n-select

A versatile select component for single and multiple selection with features like filtering, remote search, grouping, and virtual scrolling

SKILL.md

GitHub

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 number | 'responsive' undefined Maximum number of tags to display in multiple mode. Use 'responsive' for automatic adjustment.
tag boolean false Whether users can create new options by typing. Requires filterable.
fallback-option false | (value: string | number) => SelectOption value => ({ label: '' + value, value }) Function to create display option for values without matching options. Set to false to disable.
show boolean undefined Controlled visibility of the dropdown menu.
show-arrow boolean true Whether to show the dropdown arrow icon.
show-on-focus boolean false Whether to show the menu when the input is focused.
consistent-menu-width boolean true Whether the menu width matches the input width. Setting to false disables virtual scroll.
virtual-scroll boolean true Whether to enable virtual scrolling for better performance with large option lists.
placement 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-start' | 'bottom' | 'bottom-end' | 'left-start' | 'left' | 'left-end' 'bottom-start' Placement of the dropdown menu.
default-value Array<string | number> | string | number | null null Default uncontrolled value.
status 'success' | 'warning' | 'error' undefined Validation status of the select.
filter (pattern: string, option: Object) => boolean Default string search Custom filter function for searching options.
clear-filter-after-select boolean true Whether to clear the search filter after selecting an option in multiple mode.
clear-created-options-on-clear boolean true Whether to clear user-created options when clearing the selection (with tag and clearable).
ignore-composition boolean true Whether to ignore IME composition events when filtering.
keyboard boolean true Whether to enable keyboard navigation.
show-checkmark boolean true Whether to show a checkmark on selected options.
reset-menu-on-options-change boolean true Whether to reset menu state (e.g., scroll position) when options change.
label-field string 'label' Field name to use for option labels.
value-field string 'value' Field name to use for option values.
children-field string 'children' Field name for group option children.
input-props InputHTMLAttributes undefined Additional attributes for the input element.
menu-props HTMLAttributes undefined Additional attributes for the menu element.
scrollbar-props ScrollbarProps undefined Props for the internal scrollbar component.
ellipsis-tag-popover-props PopoverProps undefined Props for the popover showing truncated tags.
node-props (option: SelectOption | SelectGroupOption) => object undefined Function to generate DOM attributes for each option.
render-label (option: SelectOption | SelectGroupOption, selected: boolean) => VNodeChild undefined Custom render function for option labels.
render-option (info: { node: VNode, option: SelectOption | SelectGroupOption, selected: boolean }) => VNodeChild undefined Custom render function for entire options.
render-tag (props: { option: SelectBaseOption, handleClose: () => void }) => VNodeChild undefined Custom render function for selected tags.
to string | HTMLElement | false body Container element for the dropdown menu. Use false to keep it inline.
on-create (label: string) => SelectOption label => ({ label, value: label }) Function to create a new option when using tag mode.

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

  1. Use Virtual Scroll: For large datasets (100+ options), keep virtual-scroll enabled (default) for optimal performance.

  2. Limit Options: When possible, limit the number of options displayed at once, especially for remote search scenarios.

  3. 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

  1. Provide Clear Feedback: Use loading state during async operations to inform users that data is being fetched.

  2. Use Meaningful Placeholders: Provide clear placeholder text that guides users on what to select or search.

  3. 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.

  4. Enable Clearable: Allow users to easily clear their selection, especially for single-select scenarios.

Data Handling

  1. Unique Values: Ensure each option has a unique value property to prevent unexpected behavior.

  2. Handle Fallback Options: Decide whether to show fallback options for values without matching options. Set fallback-option="false" to automatically clear invalid values.

  3. 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

  1. Keyboard Navigation: Keep keyboard enabled (default) to allow users to navigate options using arrow keys.

  2. Focus Management: Use the focus and blur methods for programmatic control when needed.

Form Integration

  1. Validation Status: Use the status prop to show validation feedback (success, warning, error).

  2. Disabled State: Use disabled prop to prevent selection when the field should be read-only.

  3. Default Values: Set appropriate default-value for uncontrolled usage scenarios.