Skip to content
Last update: June 24, 2025

VcSelect Component

The VcSelect component is a molecule used throughout the VC-Shell framework for creating customizable dropdown selection fields. It supports single and multiple selections, searchable options, custom styling, and async data loading.

Storybook

VcSelect Storybook

Basic Usage

<template>
  <VcSelect
    v-model="selectedValue"
    :options="options"
    label="Select an option"
    placeholder="Choose from the list"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedValue = ref(null);
const options = [
  { id: 1, title: 'Option 1' },
  { id: 2, title: 'Option 2' },
  { id: 3, title: 'Option 3' }
];
</script>

Props

Prop Type Default Description
modelValue any undefined Value of the select (v-model)
options Array<any> \| Function [] Options array or async function returning options
optionValue string \| Function 'id' Property name or function to get option value
optionLabel string \| Function 'title' Property name or function to get option label
label string undefined Label displayed above the select
placeholder string undefined Placeholder text when no value is selected
debounce number 300 Debounce time for search input (in milliseconds)
clearable boolean false Whether the value can be cleared
emitValue boolean true Emit only the value instead of the entire option
searchable boolean false Enable searching/filtering options
multiple boolean false Allow selecting multiple values
disabled boolean false Disable the select component
required boolean false Mark the select as required (shows asterisk)
error boolean false Show the select in error state
errorMessage string undefined Error message to display below the select
hint string undefined Hint text to display below the select
tooltip string undefined Tooltip text to display next to the label
prefix string undefined Prefix text inside the select field
suffix string undefined Suffix text inside the select field
size 'default' \| 'small' 'default' Size of the select field
mapOptions boolean true Map labels of model from options array
loading boolean false Show loading state
outline boolean true Whether to show an outline around the select
multilanguage boolean false Whether the label should show multilanguage icon
currentLanguage string undefined Current language code for multilanguage support

Events

Event Payload Description
update:modelValue any Emitted when the selected value changes
click - Emitted when the select is clicked
clear - Emitted when the select value is cleared
search string Emitted when the search term changes (if searchable)

Slots

Slot Props Description
control { toggleHandler: () => void } Custom control element
prepend - Content to prepend outside the select field
prepend-inner - Content to prepend inside the select field
append - Content to append outside the select field
append-inner - Content to append inside the select field
option { option, index, selected } Custom option rendering in dropdown
selected-item { opt, index } Custom rendering for selected item(s)
error - Custom error message rendering
hint - Custom hint rendering
no-options - Content shown when no options are available

CSS Variables

The select component uses CSS variables for theming, which can be customized:

:root {
  /* Basic appearance */
  --select-height: 36px;                           /* Default height of select field */
  --select-height-small: 28px;                     /* Height of select field when size="small" */
  --select-border-radius: 4px;                     /* Border radius of the select field */
  --select-border-color: var(--neutrals-300);      /* Border color in normal state */
  --select-text-color: var(--neutrals-800);        /* Text color of selected value */
  --select-padding: 10px;                          /* Horizontal padding inside the select field */
  --select-background-color: var(--additional-50); /* Background color of the select field */

  /* Placeholder and icons */
  --select-placeholder-color: var(--neutrals-400); /* Color of placeholder text */
  --select-chevron-color: var(--primary-500);      /* Color of dropdown chevron icon */
  --select-chevron-color-hover: var(--primary-600); /* Color of dropdown chevron icon on hover */
  --select-clear-color: var(--primary-500);        /* Color of clear button */
  --select-clear-color-hover: var(--primary-600);  /* Color of clear button on hover */
  --select-loading-color: var(--info-500);         /* Color of loading spinner */

  /* Dropdown and options */
  --select-option-background-color-hover: var(--accent-100);   /* Background of option on hover */
  --select-option-background-color-selected: var(--accent-200); /* Background of selected option */
  --select-multiple-options-background-color: var(--additional-50); /* Background of multiple selected tags */
  --select-multiple-options-border-color: var(--secondary-200); /* Border color of multiple selected tags */
  --select-border-color-input: var(--secondary-200); /* Border color of search input in dropdown */
  --select-search-background-color: var(--additional-50); /* Background color of search field */

  /* Focus state */
  --select-border-color-focus: var(--primary-100); /* Border color when select is focused */

  /* Disabled state */
  --select-background-color-disabled: var(--neutrals-200); /* Background color when disabled */
  --select-disabled-text-color: var(--neutrals-500);       /* Text color when disabled */

  /* Error state */
  --select-border-color-error: var(--danger-500);  /* Border color when in error state */
}

Examples

Basic Select with Objects

<template>
  <VcSelect
    v-model="selectedUser"
    :options="users"
    optionValue="id"
    optionLabel="name"
    label="Select User"
    placeholder="Choose a user"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedUser = ref(null);
const users = [
  { id: 1, name: 'John Doe' },
  { id: 2, name: 'Jane Smith' },
  { id: 3, name: 'Robert Johnson' }
];
</script>

Multiple Select

<template>
  <VcSelect
    v-model="selectedTags"
    :options="tags"
    label="Select Tags"
    placeholder="Choose tags"
    :multiple="true"
    :clearable="true"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedTags = ref([]);
const tags = [
  'JavaScript', 'TypeScript', 'Vue', 'React', 'Angular', 'Node.js'
];
</script>

Searchable Select

<template>
  <VcSelect
    v-model="selectedProduct"
    :options="products"
    label="Select Product"
    placeholder="Search products"
    :searchable="true"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedProduct = ref(null);
const products = [
  { id: 1, title: 'Laptop' },
  { id: 2, title: 'Smartphone' },
  { id: 3, title: 'Tablet' },
  { id: 4, title: 'Smartwatch' },
  { id: 5, title: 'Headphones' }
];
</script>

Async Options Loading

<template>
  <VcSelect
    v-model="selectedUser"
    :options="fetchUsers"
    label="Select User"
    placeholder="Search users"
    :searchable="true"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedUser = ref(null);

const fetchUsers = async (keyword = "", skip = 0) => {
  // Simulate API call
  await new Promise(resolve => setTimeout(resolve, 500));

  const allUsers = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' },
    { id: 3, name: 'Robert Johnson' },
    { id: 4, name: 'Emily Davis' },
    { id: 5, name: 'Michael Brown' }
  ];

  // Filter users based on keyword
  const filteredUsers = keyword 
    ? allUsers.filter(user => user.name.toLowerCase().includes(keyword.toLowerCase()))
    : allUsers;

  // Return paginated results
  return {
    results: filteredUsers.slice(skip, skip + 10),
    total: filteredUsers.length
  };
};
</script>

Custom Value and Label Functions

<template>
  <VcSelect
    v-model="selectedCountry"
    :options="countries"
    :option-value="getOptionValue"
    :option-label="getOptionLabel"
    label="Select Country"
    placeholder="Choose a country"
  />
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect } from '@vc-shell/framework';

const selectedCountry = ref(null);
const countries = [
  { code: 'US', name: 'United States' },
  { code: 'CA', name: 'Canada' },
  { code: 'UK', name: 'United Kingdom' },
  { code: 'AU', name: 'Australia' },
  { code: 'JP', name: 'Japan' }
];

const getOptionValue = (option) => option.code;
const getOptionLabel = (option) => `${option.name} (${option.code})`;
</script>

Custom Option Rendering

<template>
  <VcSelect
    v-model="selectedStatus"
    :options="statuses"
    label="Select Status"
    placeholder="Choose a status"
  >
    <template #option="{ option, selected }">
      <div class="tw-flex tw-items-center tw-gap-2">
        <span 
          class="tw-w-3 tw-h-3 tw-rounded-full" 
          :style="{ backgroundColor: getStatusColor(option.color) }"
        ></span>
        <span>{{ option.label }}</span>
        <VcIcon 
          v-if="selected" 
          icon="material-check" 
          class="tw-ml-auto tw-text-[color:var(--primary-500)]" 
          size="s"
        />
      </div>
    </template>
  </VcSelect>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { VcSelect, VcIcon } from '@vc-shell/framework';

const selectedStatus = ref(null);
const statuses = [
  { id: 'active', label: 'Active', color: 'green' },
  { id: 'pending', label: 'Pending', color: 'orange' },
  { id: 'inactive', label: 'Inactive', color: 'gray' },
  { id: 'error', label: 'Error', color: 'red' }
];

const getStatusColor = (color) => {
  const colors = {
    green: 'var(--success-500)',
    orange: 'var(--warning-500)',
    gray: 'var(--neutrals-400)',
    red: 'var(--error-500)'
  };
  return colors[color] || 'var(--neutrals-500)';
};
</script>

With Validation

<template>
  <form @submit.prevent="submitForm">
    <VcSelect
      v-model="selectedCategory"
      :options="categories"
      label="Product Category"
      placeholder="Select a category"
      :error="errors.category"
      :error-message="errorMessages.category"
      required
    />

    <VcButton 
      type="submit" 
      class="tw-mt-4"
    >
      Submit
    </VcButton>
  </form>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { VcSelect, VcButton } from '@vc-shell/framework';

const selectedCategory = ref(null);
const categories = [
  { id: 1, title: 'Electronics' },
  { id: 2, title: 'Clothing' },
  { id: 3, title: 'Home & Garden' },
  { id: 4, title: 'Books' },
  { id: 5, title: 'Sports' }
];

const errors = reactive({
  category: false
});

const errorMessages = reactive({
  category: ''
});

function submitForm() {
  // Reset errors
  errors.category = false;
  errorMessages.category = '';

  // Validate
  if (!selectedCategory.value) {
    errors.category = true;
    errorMessages.category = 'Please select a category';
    return;
  }

  // Form submission logic
  console.log('Form submitted with category:', selectedCategory.value);
}
</script>

With Custom Control

<template>
  <VcSelect
    v-model="selectedOption"
    :options="options"
    label="Custom Control Select"
  >
    <template #control="{ toggleHandler }">
      <VcButton 
        variant="outlined" 
        class="tw-w-full"
        @click="toggleHandler"
      >
        <template #prepend>
          <VcIcon icon="material-list" />
        </template>
        {{ selectedOption ? getSelectedLabel() : 'Click to select' }}
      </VcButton>
    </template>
  </VcSelect>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue';
import { VcSelect, VcButton, VcIcon } from '@vc-shell/framework';

const selectedOption = ref(null);
const options = [
  { id: 1, title: 'Option 1' },
  { id: 2, title: 'Option 2' },
  { id: 3, title: 'Option 3' }
];

function getSelectedLabel() {
  const selected = options.find(opt => opt.id === selectedOption.value);
  return selected ? selected.title : '';
}
</script>