Skip to content

Select

Stable

Allow users to choose one or more options from a dropdown list.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption } from '@holistics/design-system'

const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
] as const satisfies SelectOption[]

const value = ref<typeof options[number]['value']>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
  />
</template>

Examples

Option Types & Grouped Options

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption, type SelectValue } from '@holistics/design-system'

const options = [
  {
    stickyTop: true, value: 101, label: 'Top Option 1', tooltip: 'Lorem ipsum donor',
  },
  {
    stickyTop: true,
    value: 102,
    label: 'Top Option 2',
    tooltip: 'Some tips at a group',
    children: [
      { stickyTop: true, value: '102.1', label: 'Top Option 2.1' },
      {
        stickyTop: true, value: '102.2', label: 'Inner-Bell', icon: 'bell', action: () => { console.log('Inner-bell logged!') },
      },
    ],
    initialExpanded: true,
  },
  {
    stickyTop: true,
    value: 103,
    label: 'Top Action Option',
    icon: 'function',
    action: (option) => { alert(`You clicked an option with value: "${option.value}" and label: "${option.label}"`) },
    tooltip: { content: '<em>I can be HTML, too!</em>', html: true },
  },
  { value: 1, label: 'Option 1', tooltip: 'I can be anywhere!' },
  { value: 2, label: 'Option 2 (with icon)', icon: 'favorite' },
  { value: 3, label: 'Option 3', description: 'Option with description' },
  {
    value: 4, label: 'Option 4 has a VERY Very very long label', icon: 'data-set', description: 'Option with description & icon, and a very long long long long long long long long long long long description',
  },
  {
    value: 5, label: 'Option 5 (disabled)', disabled: true, tooltip: 'Disabled option only shows tooltip on hovering (not keyboad)!',
  },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { label: 'Option a.1', value: '6.1', icon: 'type/number' },
      { label: 'Option b.2', value: '6.2', icon: 'type/string' },
      {
        label: 'Option x.3',
        value: '6.3',
        children: [
          { label: 'Option d.3.1', value: '6.3.1' },
          { label: 'Option e.3.2', value: '6.3.2' },
          {
            label: 'Option x.3.3',
            value: '6.3.3',
            children: [
              { label: 'Option g.3.3.1', value: '6.3.3.1' },
              { label: 'Option h.3.3.2', value: '6.3.3.2' },
              { label: 'Option i.3.3.3', value: '6.3.3.3' },
              {
                label: 'Option x.3.3.4',
                value: '6.3.3.4',
                children: [
                  { label: 'Option k.3.3.4.1', value: '6.3.3.4.1' },
                  { label: 'Option l.3.3.4.2', value: '6.3.3.4.2' },
                  { label: 'Option 6.3.3.4.3', value: '6.3.3.4.3' },
                  { label: 'Option m.3.3.4.4', value: '6.3.3.4.4' },
                ],
              },
              { label: 'Option j.3.3.3', value: '6.3.3.5' },
            ],
          },
          { label: 'Option f.3.4', value: '6.3.4' },
        ],
        initialExpanded: true,
      },
      { label: 'Option c.4', value: '6.4' },
    ],
  },
  { value: 7, label: 'Option 7', icon: 'user' },
  { value: 8, label: 'Option 8' },
  { value: 9, label: 'Option 9' },
  { value: 10, label: 'Option 10' },
  { value: null, label: 'Option null' },
  { stickyBottom: true, value: 901, label: 'Bottom Option 1' },
  {
    stickyBottom: true,
    value: 902,
    label: 'Bottom Option 2',
    children: [
      { stickyBottom: true, value: '901.1', label: 'Bottom Option 2.1' },
      { stickyBottom: true, value: '901.2', label: 'Bottom Option 2.2' },
    ],
  },
  {
    stickyBottom: true,
    value: 903,
    label: 'Async Refresh (done after 2s)',
    icon: 'refresh',
    action: async () => {
      await new Promise((res) => { setTimeout(res, 2000) })
      alert('Done refreshing!')
    },
  },
] as const satisfies SelectOption[]

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
  />
</template>

By default, group options are not selectable. To alter this, simply set groupSelectable to true. Be aware that enabling this feature will consequently affect the following behaviors:

  • To select/deslect a group option: Click/Enter
  • To expand a group option: Click on chevron icon >/Double-click/Shift + Enter
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { OPTIONS } from './options'

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="OPTIONS"
    group-selectable
    class="w-80"
  />
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const OPTIONS = [
  {
    stickyTop: true, value: 101, label: 'Top Option 1', tooltip: 'Lorem ipsum donor',
  },
  {
    stickyTop: true,
    value: 102,
    label: 'Top Option 2',
    tooltip: 'Some tips at a group',
    children: [
      { stickyTop: true, value: '102.1', label: 'Top Option 2.1' },
      {
        stickyTop: true, value: '102.2', label: 'Inner-Bell', icon: 'bell', action: () => { console.log('Inner-bell logged!') },
      },
    ],
    initialExpanded: true,
  },
  {
    stickyTop: true,
    value: 103,
    label: 'Top Action Option',
    icon: 'function',
    action: (option) => { alert(`You clicked an option with value: "${option.value}" and label: "${option.label}"`) },
    tooltip: { content: '<em>I can be HTML, too!</em>', html: true },
  },
  { value: 1, label: 'Option 1', tooltip: 'I can be anywhere!' },
  { value: 2, label: 'Option 2 (with icon)', icon: 'favorite' },
  { value: 3, label: 'Option 3', description: 'Option with description' },
  {
    value: 4, label: 'Option 4 has a VERY Very very long label', icon: 'data-set', description: 'Option with description & icon, and a very long long long long long long long long long long long description',
  },
  {
    value: 5, label: 'Option 5 (disabled)', disabled: true, tooltip: 'Disabled option only shows tooltip on hovering (not keyboad)!',
  },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { label: 'Option a.1', value: '6.1', icon: 'type/number' },
      { label: 'Option b.2', value: '6.2', icon: 'type/string' },
      {
        label: 'Option x.3',
        value: '6.3',
        children: [
          { label: 'Option d.3.1', value: '6.3.1' },
          { label: 'Option e.3.2', value: '6.3.2' },
          {
            label: 'Option x.3.3',
            value: '6.3.3',
            children: [
              { label: 'Option g.3.3.1', value: '6.3.3.1' },
              { label: 'Option h.3.3.2', value: '6.3.3.2' },
              { label: 'Option i.3.3.3', value: '6.3.3.3' },
              {
                label: 'Option x.3.3.4',
                value: '6.3.3.4',
                children: [
                  { label: 'Option k.3.3.4.1', value: '6.3.3.4.1' },
                  { label: 'Option l.3.3.4.2', value: '6.3.3.4.2' },
                  { label: 'Option 6.3.3.4.3', value: '6.3.3.4.3' },
                  { label: 'Option m.3.3.4.4', value: '6.3.3.4.4' },
                ],
              },
              { label: 'Option j.3.3.3', value: '6.3.3.5' },
            ],
          },
          { label: 'Option f.3.4', value: '6.3.4' },
        ],
        initialExpanded: true,
      },
      { label: 'Option c.4', value: '6.4' },
    ],
  },
  { value: 7, label: 'Option 7', icon: 'user' },
  { value: 8, label: 'Option 8' },
  { value: 9, label: 'Option 9' },
  { value: 10, label: 'Option 10' },
  { value: null, label: 'Option null' },
  { stickyBottom: true, value: 901, label: 'Bottom Option 1' },
  {
    stickyBottom: true,
    value: 902,
    label: 'Bottom Option 2',
    children: [
      { stickyBottom: true, value: '901.1', label: 'Bottom Option 2.1' },
      { stickyBottom: true, value: '901.2', label: 'Bottom Option 2.2' },
    ],
  },
  {
    stickyBottom: true,
    value: 903,
    label: 'Async Refresh (done after 2s)',
    icon: 'refresh',
    action: async () => {
      await new Promise((res) => { setTimeout(res, 2000) })
      alert('Done refreshing!')
    },
  },
] as const satisfies SelectOption[]

export const ADVANCED_SEARCH_OPTIONS = [
  {
    value: 'users',
    label: 'Users',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'countries',
        label: 'Countries',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'cities',
            label: 'Cities',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'beautiful-stories', label: 'Beautiful Stories' },
              { value: 'large-libraries', label: 'Large Libraries' },
            ],
          },
          {
            value: 'families',
            label: 'Families',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'with-stories', label: 'With Stories' },
              { value: 'like-parties', label: 'Like Parties' },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'A',
    label: 'City Dwellers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'B',
        label: 'Urban City',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'C',
            label: 'Analyze Data',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'D',
                label: 'Analytical Reports',
              },
            ],
          },
          {
            value: 'E',
            label: 'Childhood Memories',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'F',
                label: "Children's Games",
              },
            ],
          },
        ],
      },
      {
        value: 'G',
        label: 'Tooth Brushing',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'H',
            label: 'Teeth Cleaning',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'I',
                label: 'Mouse Clicks',
              },
              {
                value: 'J',
                label: 'Mice Running',
              },
            ],
          },
          {
            value: 'K',
            label: 'Foot Prints',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'L',
                label: 'Feet Walking',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'M',
    label: 'Person Interview',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'N',
        label: 'People Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'O',
            label: 'Woman Power',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'P',
                label: 'Women Rights',
              },
            ],
          },
          {
            value: 'Q',
            label: 'Leaf Fall',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'R',
                label: 'Leaves Turning',
              },
            ],
          },
        ],
      },
      {
        value: 'S',
        label: 'Knife Sharp',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'T',
            label: 'Knives Set',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'U',
                label: 'Create Content',
              },
              {
                value: 'V',
                label: 'Creative Writing',
              },
            ],
          },
          {
            value: 'W',
            label: 'Develop Software',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'X',
                label: 'Development Tools',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'Y',
    label: 'Organize Events',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'Z',
        label: 'Organizational Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AA',
            label: 'Apply Pressure',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AB',
                label: 'Application Forms',
              },
            ],
          },
          {
            value: 'AC',
            label: 'Multiply Numbers',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AD',
                label: 'Multiplication Tables',
              },
            ],
          },
        ],
      },
      {
        value: 'AE',
        label: 'Identify Problems',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AF',
            label: 'Identification Cards',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AG',
                label: 'Rectify Errors',
              },
              {
                value: 'AH',
                label: 'Rectification Process',
              },
            ],
          },
          {
            value: 'AI',
            label: 'Classify Items',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AJ',
                label: 'Classification System',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AK',
    label: 'Beautiful Flowers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AL',
        label: 'Beauty Products',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AM',
            label: 'Quick Action',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AN',
                label: 'Quickly Done',
              },
            ],
          },
          {
            value: 'AO',
            label: 'Heavy Lifting',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AP',
                label: 'Heavily Loaded',
              },
            ],
          },
        ],
      },
      {
        value: 'AQ',
        label: 'Simple Solution',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AR',
            label: 'Simply Put',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AS',
                label: 'Possible outcome',
              },
              {
                value: 'AT',
                label: 'Possibly true',
              },
            ],
          },
          {
            value: 'AU',
            label: 'Electric cars',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AV',
                label: 'electricity bills',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AW',
    label: 'Dramatic plays',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AX',
        label: 'dramatically changed',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AY',
            label: 'Historic buildings',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AZ',
                label: 'historically accurate',
              },
            ],
          },
        ],
      },
    ],
  },
] as const satisfies SelectOption[]

Multiple Select

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect } from '@holistics/design-system'
import { options } from './options'

const value = ref<typeof options[number]['value']>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    multiple
    placeholder="Default..."
    class="w-80"
  />
</template>
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect } from '@holistics/design-system'
import { options } from './options'

const value = ref<typeof options[number]['value']>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    multiple="counter"
    placeholder="With Counter..."
    class="w-80"
  />
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4 (longer)' },
  { value: 5, label: 'Option 5 (even longgggeeerrr)' },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { value: '6.1', label: 'Child of Option 6' },
      { value: '6.2', label: 'Another child of Option 6', disabled: true },
      { value: '6.3', label: 'Last child of Option 6' },
    ],
  },
  { value: 7, label: 'Option 7' },
  { value: 8, label: '8' },
  { value: 9, label: 'Option 9', description: 'Some explanation for Option 9...' },
  { value: null, label: 'Option with null value' },
] as const satisfies SelectOption[]

Filterable

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { OPTIONS } from '../options'

const value = ref<SelectValue | SelectValue[]>()
</script>

<template>
  <div class="flex flex-col items-center gap-x-4 gap-y-2 md:flex-row lg:flex-col 2xl:flex-row">
    <HSelect
      v-model="value"
      :options="OPTIONS"
      filterable
      placeholder="Select..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="OPTIONS"
      multiple
      filterable
      placeholder="Select multiple..."
      class="w-80"
    />
  </div>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { OPTIONS } from '../options'

const value = ref<SelectValue | SelectValue[]>()
</script>

<template>
  <div class="flex flex-col items-center gap-x-4 gap-y-2 md:flex-row lg:flex-col 2xl:flex-row">
    <HSelect
      v-model="value"
      :options="OPTIONS"
      filterable
      filter-include-direct-children
      placeholder="Select (include direct children)..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="OPTIONS"
      multiple
      filterable
      filter-include-direct-children
      placeholder="Select multiple (include direct children)..."
      class="w-80"
    />
  </div>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { OPTIONS } from '../options'

const value = ref<SelectValue | SelectValue[]>()
</script>

<template>
  <div class="flex flex-col items-center gap-x-4 gap-y-2 md:flex-row lg:flex-col 2xl:flex-row">
    <HSelect
      v-model="value"
      :options="OPTIONS"
      filterable
      filter-include-sticky-options
      placeholder="Select (include sticky options)..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="OPTIONS"
      multiple
      filterable
      filter-include-sticky-options
      placeholder="Select multiple (include sticky options)..."
      class="w-80"
    />
  </div>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { ADVANCED_SEARCH_OPTIONS } from '../options'

const value = ref<SelectValue | SelectValue[]>()
</script>

<template>
  <div class="flex flex-col items-center gap-x-4 gap-y-2 md:flex-row lg:flex-col 2xl:flex-row">
    <HSelect
      v-model="value"
      :options="ADVANCED_SEARCH_OPTIONS"
      filterable
      filter-advanced-search
      placeholder="Select (advanced search)..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="ADVANCED_SEARCH_OPTIONS"
      multiple
      filterable
      filter-advanced-search
      placeholder="Select multiple (advanced search)..."
      class="w-80"
    />
  </div>
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const OPTIONS = [
  {
    stickyTop: true, value: 101, label: 'Top Option 1', tooltip: 'Lorem ipsum donor',
  },
  {
    stickyTop: true,
    value: 102,
    label: 'Top Option 2',
    tooltip: 'Some tips at a group',
    children: [
      { stickyTop: true, value: '102.1', label: 'Top Option 2.1' },
      {
        stickyTop: true, value: '102.2', label: 'Inner-Bell', icon: 'bell', action: () => { console.log('Inner-bell logged!') },
      },
    ],
    initialExpanded: true,
  },
  {
    stickyTop: true,
    value: 103,
    label: 'Top Action Option',
    icon: 'function',
    action: (option) => { alert(`You clicked an option with value: "${option.value}" and label: "${option.label}"`) },
    tooltip: { content: '<em>I can be HTML, too!</em>', html: true },
  },
  { value: 1, label: 'Option 1', tooltip: 'I can be anywhere!' },
  { value: 2, label: 'Option 2 (with icon)', icon: 'favorite' },
  { value: 3, label: 'Option 3', description: 'Option with description' },
  {
    value: 4, label: 'Option 4 has a VERY Very very long label', icon: 'data-set', description: 'Option with description & icon, and a very long long long long long long long long long long long description',
  },
  {
    value: 5, label: 'Option 5 (disabled)', disabled: true, tooltip: 'Disabled option only shows tooltip on hovering (not keyboad)!',
  },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { label: 'Option a.1', value: '6.1', icon: 'type/number' },
      { label: 'Option b.2', value: '6.2', icon: 'type/string' },
      {
        label: 'Option x.3',
        value: '6.3',
        children: [
          { label: 'Option d.3.1', value: '6.3.1' },
          { label: 'Option e.3.2', value: '6.3.2' },
          {
            label: 'Option x.3.3',
            value: '6.3.3',
            children: [
              { label: 'Option g.3.3.1', value: '6.3.3.1' },
              { label: 'Option h.3.3.2', value: '6.3.3.2' },
              { label: 'Option i.3.3.3', value: '6.3.3.3' },
              {
                label: 'Option x.3.3.4',
                value: '6.3.3.4',
                children: [
                  { label: 'Option k.3.3.4.1', value: '6.3.3.4.1' },
                  { label: 'Option l.3.3.4.2', value: '6.3.3.4.2' },
                  { label: 'Option 6.3.3.4.3', value: '6.3.3.4.3' },
                  { label: 'Option m.3.3.4.4', value: '6.3.3.4.4' },
                ],
              },
              { label: 'Option j.3.3.3', value: '6.3.3.5' },
            ],
          },
          { label: 'Option f.3.4', value: '6.3.4' },
        ],
        initialExpanded: true,
      },
      { label: 'Option c.4', value: '6.4' },
    ],
  },
  { value: 7, label: 'Option 7', icon: 'user' },
  { value: 8, label: 'Option 8' },
  { value: 9, label: 'Option 9' },
  { value: 10, label: 'Option 10' },
  { value: null, label: 'Option null' },
  { stickyBottom: true, value: 901, label: 'Bottom Option 1' },
  {
    stickyBottom: true,
    value: 902,
    label: 'Bottom Option 2',
    children: [
      { stickyBottom: true, value: '901.1', label: 'Bottom Option 2.1' },
      { stickyBottom: true, value: '901.2', label: 'Bottom Option 2.2' },
    ],
  },
  {
    stickyBottom: true,
    value: 903,
    label: 'Async Refresh (done after 2s)',
    icon: 'refresh',
    action: async () => {
      await new Promise((res) => { setTimeout(res, 2000) })
      alert('Done refreshing!')
    },
  },
] as const satisfies SelectOption[]

export const ADVANCED_SEARCH_OPTIONS = [
  {
    value: 'users',
    label: 'Users',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'countries',
        label: 'Countries',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'cities',
            label: 'Cities',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'beautiful-stories', label: 'Beautiful Stories' },
              { value: 'large-libraries', label: 'Large Libraries' },
            ],
          },
          {
            value: 'families',
            label: 'Families',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'with-stories', label: 'With Stories' },
              { value: 'like-parties', label: 'Like Parties' },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'A',
    label: 'City Dwellers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'B',
        label: 'Urban City',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'C',
            label: 'Analyze Data',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'D',
                label: 'Analytical Reports',
              },
            ],
          },
          {
            value: 'E',
            label: 'Childhood Memories',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'F',
                label: "Children's Games",
              },
            ],
          },
        ],
      },
      {
        value: 'G',
        label: 'Tooth Brushing',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'H',
            label: 'Teeth Cleaning',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'I',
                label: 'Mouse Clicks',
              },
              {
                value: 'J',
                label: 'Mice Running',
              },
            ],
          },
          {
            value: 'K',
            label: 'Foot Prints',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'L',
                label: 'Feet Walking',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'M',
    label: 'Person Interview',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'N',
        label: 'People Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'O',
            label: 'Woman Power',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'P',
                label: 'Women Rights',
              },
            ],
          },
          {
            value: 'Q',
            label: 'Leaf Fall',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'R',
                label: 'Leaves Turning',
              },
            ],
          },
        ],
      },
      {
        value: 'S',
        label: 'Knife Sharp',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'T',
            label: 'Knives Set',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'U',
                label: 'Create Content',
              },
              {
                value: 'V',
                label: 'Creative Writing',
              },
            ],
          },
          {
            value: 'W',
            label: 'Develop Software',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'X',
                label: 'Development Tools',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'Y',
    label: 'Organize Events',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'Z',
        label: 'Organizational Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AA',
            label: 'Apply Pressure',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AB',
                label: 'Application Forms',
              },
            ],
          },
          {
            value: 'AC',
            label: 'Multiply Numbers',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AD',
                label: 'Multiplication Tables',
              },
            ],
          },
        ],
      },
      {
        value: 'AE',
        label: 'Identify Problems',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AF',
            label: 'Identification Cards',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AG',
                label: 'Rectify Errors',
              },
              {
                value: 'AH',
                label: 'Rectification Process',
              },
            ],
          },
          {
            value: 'AI',
            label: 'Classify Items',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AJ',
                label: 'Classification System',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AK',
    label: 'Beautiful Flowers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AL',
        label: 'Beauty Products',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AM',
            label: 'Quick Action',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AN',
                label: 'Quickly Done',
              },
            ],
          },
          {
            value: 'AO',
            label: 'Heavy Lifting',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AP',
                label: 'Heavily Loaded',
              },
            ],
          },
        ],
      },
      {
        value: 'AQ',
        label: 'Simple Solution',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AR',
            label: 'Simply Put',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AS',
                label: 'Possible outcome',
              },
              {
                value: 'AT',
                label: 'Possibly true',
              },
            ],
          },
          {
            value: 'AU',
            label: 'Electric cars',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AV',
                label: 'electricity bills',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AW',
    label: 'Dramatic plays',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AX',
        label: 'dramatically changed',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AY',
            label: 'Historic buildings',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AZ',
                label: 'historically accurate',
              },
            ],
          },
        ],
      },
    ],
  },
] as const satisfies SelectOption[]

Filterable (async)

You can listen on the search text and decide what options are rendered accordingly with the @search event.

WARNING

When using @search, the default searching behavior of the filterable* and creatable props won't be applied anymore! This means you need to specify the searching mechanism on your own!

vue
<script setup lang="ts">
import { type Ref, ref } from 'vue'
import { HSelect, type SelectValue, type SelectOption } from '@holistics/design-system'
import { OPTIONS as baseOptions } from './options'

const options = ref(baseOptions) as Ref<SelectOption[]>

const value = ref<SelectValue | SelectValue[]>()

async function onSearch (text: string) {
  if (!text) {
    options.value = baseOptions
    return
  }

  await new Promise((res) => { setTimeout(res, 3000) })
  options.value = baseOptions.filter((o) => o.label.includes(text)) // [!code warning] // 🗃️️ Should be a list returned from the server
}
</script>

<template>
  <div class="space-y-4">
    <HSelect
      v-model="value"
      :options="options"
      filterable
      class="w-80"
      @search="onSearch"
    />

    <HSelect
      v-model="value"
      :options="options"
      mutliple
      filterable
      placeholder="Select multiple..."
      class="w-80"
      @search="onSearch"
    />
  </div>
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const OPTIONS = [
  {
    stickyTop: true, value: 101, label: 'Top Option 1', tooltip: 'Lorem ipsum donor',
  },
  {
    stickyTop: true,
    value: 102,
    label: 'Top Option 2',
    tooltip: 'Some tips at a group',
    children: [
      { stickyTop: true, value: '102.1', label: 'Top Option 2.1' },
      {
        stickyTop: true, value: '102.2', label: 'Inner-Bell', icon: 'bell', action: () => { console.log('Inner-bell logged!') },
      },
    ],
    initialExpanded: true,
  },
  {
    stickyTop: true,
    value: 103,
    label: 'Top Action Option',
    icon: 'function',
    action: (option) => { alert(`You clicked an option with value: "${option.value}" and label: "${option.label}"`) },
    tooltip: { content: '<em>I can be HTML, too!</em>', html: true },
  },
  { value: 1, label: 'Option 1', tooltip: 'I can be anywhere!' },
  { value: 2, label: 'Option 2 (with icon)', icon: 'favorite' },
  { value: 3, label: 'Option 3', description: 'Option with description' },
  {
    value: 4, label: 'Option 4 has a VERY Very very long label', icon: 'data-set', description: 'Option with description & icon, and a very long long long long long long long long long long long description',
  },
  {
    value: 5, label: 'Option 5 (disabled)', disabled: true, tooltip: 'Disabled option only shows tooltip on hovering (not keyboad)!',
  },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { label: 'Option a.1', value: '6.1', icon: 'type/number' },
      { label: 'Option b.2', value: '6.2', icon: 'type/string' },
      {
        label: 'Option x.3',
        value: '6.3',
        children: [
          { label: 'Option d.3.1', value: '6.3.1' },
          { label: 'Option e.3.2', value: '6.3.2' },
          {
            label: 'Option x.3.3',
            value: '6.3.3',
            children: [
              { label: 'Option g.3.3.1', value: '6.3.3.1' },
              { label: 'Option h.3.3.2', value: '6.3.3.2' },
              { label: 'Option i.3.3.3', value: '6.3.3.3' },
              {
                label: 'Option x.3.3.4',
                value: '6.3.3.4',
                children: [
                  { label: 'Option k.3.3.4.1', value: '6.3.3.4.1' },
                  { label: 'Option l.3.3.4.2', value: '6.3.3.4.2' },
                  { label: 'Option 6.3.3.4.3', value: '6.3.3.4.3' },
                  { label: 'Option m.3.3.4.4', value: '6.3.3.4.4' },
                ],
              },
              { label: 'Option j.3.3.3', value: '6.3.3.5' },
            ],
          },
          { label: 'Option f.3.4', value: '6.3.4' },
        ],
        initialExpanded: true,
      },
      { label: 'Option c.4', value: '6.4' },
    ],
  },
  { value: 7, label: 'Option 7', icon: 'user' },
  { value: 8, label: 'Option 8' },
  { value: 9, label: 'Option 9' },
  { value: 10, label: 'Option 10' },
  { value: null, label: 'Option null' },
  { stickyBottom: true, value: 901, label: 'Bottom Option 1' },
  {
    stickyBottom: true,
    value: 902,
    label: 'Bottom Option 2',
    children: [
      { stickyBottom: true, value: '901.1', label: 'Bottom Option 2.1' },
      { stickyBottom: true, value: '901.2', label: 'Bottom Option 2.2' },
    ],
  },
  {
    stickyBottom: true,
    value: 903,
    label: 'Async Refresh (done after 2s)',
    icon: 'refresh',
    action: async () => {
      await new Promise((res) => { setTimeout(res, 2000) })
      alert('Done refreshing!')
    },
  },
] as const satisfies SelectOption[]

export const ADVANCED_SEARCH_OPTIONS = [
  {
    value: 'users',
    label: 'Users',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'countries',
        label: 'Countries',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'cities',
            label: 'Cities',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'beautiful-stories', label: 'Beautiful Stories' },
              { value: 'large-libraries', label: 'Large Libraries' },
            ],
          },
          {
            value: 'families',
            label: 'Families',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'with-stories', label: 'With Stories' },
              { value: 'like-parties', label: 'Like Parties' },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'A',
    label: 'City Dwellers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'B',
        label: 'Urban City',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'C',
            label: 'Analyze Data',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'D',
                label: 'Analytical Reports',
              },
            ],
          },
          {
            value: 'E',
            label: 'Childhood Memories',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'F',
                label: "Children's Games",
              },
            ],
          },
        ],
      },
      {
        value: 'G',
        label: 'Tooth Brushing',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'H',
            label: 'Teeth Cleaning',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'I',
                label: 'Mouse Clicks',
              },
              {
                value: 'J',
                label: 'Mice Running',
              },
            ],
          },
          {
            value: 'K',
            label: 'Foot Prints',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'L',
                label: 'Feet Walking',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'M',
    label: 'Person Interview',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'N',
        label: 'People Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'O',
            label: 'Woman Power',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'P',
                label: 'Women Rights',
              },
            ],
          },
          {
            value: 'Q',
            label: 'Leaf Fall',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'R',
                label: 'Leaves Turning',
              },
            ],
          },
        ],
      },
      {
        value: 'S',
        label: 'Knife Sharp',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'T',
            label: 'Knives Set',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'U',
                label: 'Create Content',
              },
              {
                value: 'V',
                label: 'Creative Writing',
              },
            ],
          },
          {
            value: 'W',
            label: 'Develop Software',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'X',
                label: 'Development Tools',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'Y',
    label: 'Organize Events',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'Z',
        label: 'Organizational Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AA',
            label: 'Apply Pressure',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AB',
                label: 'Application Forms',
              },
            ],
          },
          {
            value: 'AC',
            label: 'Multiply Numbers',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AD',
                label: 'Multiplication Tables',
              },
            ],
          },
        ],
      },
      {
        value: 'AE',
        label: 'Identify Problems',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AF',
            label: 'Identification Cards',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AG',
                label: 'Rectify Errors',
              },
              {
                value: 'AH',
                label: 'Rectification Process',
              },
            ],
          },
          {
            value: 'AI',
            label: 'Classify Items',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AJ',
                label: 'Classification System',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AK',
    label: 'Beautiful Flowers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AL',
        label: 'Beauty Products',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AM',
            label: 'Quick Action',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AN',
                label: 'Quickly Done',
              },
            ],
          },
          {
            value: 'AO',
            label: 'Heavy Lifting',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AP',
                label: 'Heavily Loaded',
              },
            ],
          },
        ],
      },
      {
        value: 'AQ',
        label: 'Simple Solution',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AR',
            label: 'Simply Put',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AS',
                label: 'Possible outcome',
              },
              {
                value: 'AT',
                label: 'Possibly true',
              },
            ],
          },
          {
            value: 'AU',
            label: 'Electric cars',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AV',
                label: 'electricity bills',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AW',
    label: 'Dramatic plays',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AX',
        label: 'dramatically changed',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AY',
            label: 'Historic buildings',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AZ',
                label: 'historically accurate',
              },
            ],
          },
        ],
      },
    ],
  },
] as const satisfies SelectOption[]

Create Options Dynamically

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption } from '@holistics/design-system'

const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
] as const satisfies SelectOption[]

const value = ref<typeof options[number]['value']>()
</script>

<template>
  <div class="space-y-4">
    <HSelect
      v-model="value"
      :options="options"
      filterable
      createable
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      filterable
      createable
      placeholder="Select multiple..."
      class="w-80"
    />
  </div>
</template>

Clearable

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption } from '@holistics/design-system'

const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
] as const satisfies SelectOption[]

const value = ref<typeof options[number]['value']>()
</script>

<template>
  <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-1 2xl:grid-cols-2">
    <HSelect
      v-model="value"
      :options="options"
      clearable
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      filterable
      clearable
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      clearable
      placeholder="Select multiple..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      filterable
      clearable
      placeholder="Select multiple..."
      class="w-80"
    />
  </div>
</template>

Inlined

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { OPTIONS } from './options'

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="OPTIONS"
    inline
    filterable
    class="w-80"
  />
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const OPTIONS = [
  {
    stickyTop: true, value: 101, label: 'Top Option 1', tooltip: 'Lorem ipsum donor',
  },
  {
    stickyTop: true,
    value: 102,
    label: 'Top Option 2',
    tooltip: 'Some tips at a group',
    children: [
      { stickyTop: true, value: '102.1', label: 'Top Option 2.1' },
      {
        stickyTop: true, value: '102.2', label: 'Inner-Bell', icon: 'bell', action: () => { console.log('Inner-bell logged!') },
      },
    ],
    initialExpanded: true,
  },
  {
    stickyTop: true,
    value: 103,
    label: 'Top Action Option',
    icon: 'function',
    action: (option) => { alert(`You clicked an option with value: "${option.value}" and label: "${option.label}"`) },
    tooltip: { content: '<em>I can be HTML, too!</em>', html: true },
  },
  { value: 1, label: 'Option 1', tooltip: 'I can be anywhere!' },
  { value: 2, label: 'Option 2 (with icon)', icon: 'favorite' },
  { value: 3, label: 'Option 3', description: 'Option with description' },
  {
    value: 4, label: 'Option 4 has a VERY Very very long label', icon: 'data-set', description: 'Option with description & icon, and a very long long long long long long long long long long long description',
  },
  {
    value: 5, label: 'Option 5 (disabled)', disabled: true, tooltip: 'Disabled option only shows tooltip on hovering (not keyboad)!',
  },
  {
    value: 6,
    label: 'Option 6',
    children: [
      { label: 'Option a.1', value: '6.1', icon: 'type/number' },
      { label: 'Option b.2', value: '6.2', icon: 'type/string' },
      {
        label: 'Option x.3',
        value: '6.3',
        children: [
          { label: 'Option d.3.1', value: '6.3.1' },
          { label: 'Option e.3.2', value: '6.3.2' },
          {
            label: 'Option x.3.3',
            value: '6.3.3',
            children: [
              { label: 'Option g.3.3.1', value: '6.3.3.1' },
              { label: 'Option h.3.3.2', value: '6.3.3.2' },
              { label: 'Option i.3.3.3', value: '6.3.3.3' },
              {
                label: 'Option x.3.3.4',
                value: '6.3.3.4',
                children: [
                  { label: 'Option k.3.3.4.1', value: '6.3.3.4.1' },
                  { label: 'Option l.3.3.4.2', value: '6.3.3.4.2' },
                  { label: 'Option 6.3.3.4.3', value: '6.3.3.4.3' },
                  { label: 'Option m.3.3.4.4', value: '6.3.3.4.4' },
                ],
              },
              { label: 'Option j.3.3.3', value: '6.3.3.5' },
            ],
          },
          { label: 'Option f.3.4', value: '6.3.4' },
        ],
        initialExpanded: true,
      },
      { label: 'Option c.4', value: '6.4' },
    ],
  },
  { value: 7, label: 'Option 7', icon: 'user' },
  { value: 8, label: 'Option 8' },
  { value: 9, label: 'Option 9' },
  { value: 10, label: 'Option 10' },
  { value: null, label: 'Option null' },
  { stickyBottom: true, value: 901, label: 'Bottom Option 1' },
  {
    stickyBottom: true,
    value: 902,
    label: 'Bottom Option 2',
    children: [
      { stickyBottom: true, value: '901.1', label: 'Bottom Option 2.1' },
      { stickyBottom: true, value: '901.2', label: 'Bottom Option 2.2' },
    ],
  },
  {
    stickyBottom: true,
    value: 903,
    label: 'Async Refresh (done after 2s)',
    icon: 'refresh',
    action: async () => {
      await new Promise((res) => { setTimeout(res, 2000) })
      alert('Done refreshing!')
    },
  },
] as const satisfies SelectOption[]

export const ADVANCED_SEARCH_OPTIONS = [
  {
    value: 'users',
    label: 'Users',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'countries',
        label: 'Countries',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'cities',
            label: 'Cities',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'beautiful-stories', label: 'Beautiful Stories' },
              { value: 'large-libraries', label: 'Large Libraries' },
            ],
          },
          {
            value: 'families',
            label: 'Families',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              { value: 'with-stories', label: 'With Stories' },
              { value: 'like-parties', label: 'Like Parties' },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'A',
    label: 'City Dwellers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'B',
        label: 'Urban City',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'C',
            label: 'Analyze Data',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'D',
                label: 'Analytical Reports',
              },
            ],
          },
          {
            value: 'E',
            label: 'Childhood Memories',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'F',
                label: "Children's Games",
              },
            ],
          },
        ],
      },
      {
        value: 'G',
        label: 'Tooth Brushing',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'H',
            label: 'Teeth Cleaning',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'I',
                label: 'Mouse Clicks',
              },
              {
                value: 'J',
                label: 'Mice Running',
              },
            ],
          },
          {
            value: 'K',
            label: 'Foot Prints',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'L',
                label: 'Feet Walking',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'M',
    label: 'Person Interview',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'N',
        label: 'People Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'O',
            label: 'Woman Power',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'P',
                label: 'Women Rights',
              },
            ],
          },
          {
            value: 'Q',
            label: 'Leaf Fall',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'R',
                label: 'Leaves Turning',
              },
            ],
          },
        ],
      },
      {
        value: 'S',
        label: 'Knife Sharp',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'T',
            label: 'Knives Set',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'U',
                label: 'Create Content',
              },
              {
                value: 'V',
                label: 'Creative Writing',
              },
            ],
          },
          {
            value: 'W',
            label: 'Develop Software',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'X',
                label: 'Development Tools',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'Y',
    label: 'Organize Events',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'Z',
        label: 'Organizational Skills',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AA',
            label: 'Apply Pressure',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AB',
                label: 'Application Forms',
              },
            ],
          },
          {
            value: 'AC',
            label: 'Multiply Numbers',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AD',
                label: 'Multiplication Tables',
              },
            ],
          },
        ],
      },
      {
        value: 'AE',
        label: 'Identify Problems',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AF',
            label: 'Identification Cards',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AG',
                label: 'Rectify Errors',
              },
              {
                value: 'AH',
                label: 'Rectification Process',
              },
            ],
          },
          {
            value: 'AI',
            label: 'Classify Items',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AJ',
                label: 'Classification System',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AK',
    label: 'Beautiful Flowers',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AL',
        label: 'Beauty Products',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AM',
            label: 'Quick Action',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AN',
                label: 'Quickly Done',
              },
            ],
          },
          {
            value: 'AO',
            label: 'Heavy Lifting',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AP',
                label: 'Heavily Loaded',
              },
            ],
          },
        ],
      },
      {
        value: 'AQ',
        label: 'Simple Solution',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AR',
            label: 'Simply Put',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AS',
                label: 'Possible outcome',
              },
              {
                value: 'AT',
                label: 'Possibly true',
              },
            ],
          },
          {
            value: 'AU',
            label: 'Electric cars',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AV',
                label: 'electricity bills',
              },
            ],
          },
        ],
      },
    ],
  },
  {
    value: 'AW',
    label: 'Dramatic plays',
    initialExpanded: true,
    icon: 'canvas',
    children: [
      {
        value: 'AX',
        label: 'dramatically changed',
        initialExpanded: true,
        icon: 'data-set',
        children: [
          {
            value: 'AY',
            label: 'Historic buildings',
            initialExpanded: true,
            icon: 'data-model',
            children: [
              {
                value: 'AZ',
                label: 'historically accurate',
              },
            ],
          },
        ],
      },
    ],
  },
] as const satisfies SelectOption[]

With Refresh Button

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption } from '@holistics/design-system'

const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
] as const satisfies SelectOption[]

const value = ref<typeof options[number]['value']>()

async function onRefresh () {
  await new Promise((res) => { setTimeout(res, 2000) })
}
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
    @refresh="onRefresh"
  />
</template>

Infinite Scroll

vue
<script setup lang="ts">
import { type Ref, ref } from 'vue'
import { HSelect, type SelectOption, type SelectValue } from '@holistics/design-system'

const options = ref([
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
  { value: 5, label: 'Option 5' },
  { value: 6, label: 'Option 6' },
  { value: 7, label: 'Option 7' },
  { value: 8, label: 'Option 8' },
]) as Ref<SelectOption[]>

const value = ref<SelectValue>()

const page = ref(0)
async function onScrollBottom () {
  await new Promise((res) => { setTimeout(res, 3000) })

  const temp = options.value.slice()
  // [!code warning] // 🗃️ Should be a list returned from the server
  temp.push(...Array.from({ length: 5 }, (_, i) => {
    const num = page.value * 5 + (i + 1)
    return {
      value: `appended-${num}`,
      label: `Appended Option ${num}`,
    }
  }))
  options.value = temp

  page.value += 1
}
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
    preserve-focus
    @scroll-bottom="onScrollBottom"
  />
</template>

Themes

vue
<script setup lang="ts">
import { type Ref, ref } from 'vue'
import { HSelect, type SelectOption, type SelectValue } from '@holistics/design-system'

const options = ref([
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  { value: 3, label: 'Option 3', disabled: true },
  { value: 4, label: 'Option 4' },
]) as Ref<SelectOption[]>

const value = ref<SelectValue>()
</script>

<template>
  <div class="flex flex-col items-center gap-4 md:flex-row lg:flex-col 2xl:flex-row">
    <HSelect
      v-model="value"
      :options="options"
      placeholder="Default..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      placeholder="Underline..."
      theme="underline"
      class="w-80"
    />
  </div>
</template>

Custom CSS & Styles for Options

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption, type SelectValue } from '@holistics/design-system'

const options = [
  {
    stickyTop: true, value: '1', label: 'A big padding option!', style: { padding: '1rem 1.5rem' },
  },
  {
    value: '2',
    label: 'Open me',
    children: [
      { value: '2-1', label: 'A little bit italic', class: 'italic' },
      { value: '2-2', label: 'And super large text!', style: { 'font-size': '2rem', 'line-height': '1.5' } },
    ],
  },
] as const satisfies SelectOption[]

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
  />
</template>

Customize Options individually

vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  HSelect, HTextHighlight, type SelectOption, type SelectValue,
} from '@holistics/design-system'

const options = [
  { value: 'default-option', label: 'Default Option' },
  {
    value: 'mimic-default-option',
    label: 'Mimic Default Option',
    slot: 'mimic-default-option',
    tooltip: 'This slotted option is trying to mimic all styles and behaviors of default Option!',
  },
  {
    stickyBottom: true,
    value: 'footer',
    label: 'Footer',
    slot: 'footer',
    disabled: true,
  },
] as const satisfies SelectOption[]

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
  >
    <template #mimic-default-option="{ option, focused, selected, searchText, onClick, onMouseover }">
      <div
        class="hui-select-option"
        :class="[
          { focused, selected },
          !selected && 'text-green-600',
        ]"
        :data-value="option.value"
        @click="onClick"
        @mouseover="onMouseover"
      >
        <HTextHighlight
          :text="option.label"
          :highlight="searchText"
        />
      </div>
    </template>

    <template #footer>
      <div class="p-2">
        A footer that has a <a
          href="https://holistics.io"
          target="_blank"
          class="text-blue-600 underline"
        >link</a>!
      </div>
    </template>
  </HSelect>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectValue } from '@holistics/design-system'
import { options } from './optionsSubmenu'

const value = ref<SelectValue>()
</script>

<template>
  <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-1 2xl:grid-cols-2">
    <HSelect
      v-model="value"
      :options="options"
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      placeholder="Select multiple..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      filterable
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      filterable
      placeholder="Select multiple..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      filterable
      filter-include-direct-children
      placeholder="Select (include direct children)..."
      class="w-80"
    />

    <HSelect
      v-model="value"
      :options="options"
      multiple
      filterable
      filter-include-direct-children
      placeholder="Select multiple (include direct children)..."
      class="w-80"
    />
  </div>
</template>
ts
import type { SelectOption } from '@holistics/design-system'

export const options = [
  { value: 1, label: 'Option 1' },
  { value: 2, label: 'Option 2' },
  {
    value: 'more-options',
    label: 'More Options',
    childrenAsSubmenu: true,
    children: [
      { value: '3.1', label: 'Option 3.1' },
      { value: '3.2', label: 'Option 3.2' },
      {
        value: '3.3-group',
        label: 'Option 3.3 (Group)',
        children: [
          { value: '3.x.1', label: 'Option 3.x.1' },
          { value: '3.x.2', label: 'Option 3.x.2' },
          { value: '3.x.3', label: 'Option 3.x.3' },
          {
            value: '3.3.4-more-options',
            label: 'More Options',
            childrenAsSubmenu: true,
            children: [
              { value: '3.3.4.1', label: 'Option 3.3.4.1' },
              {
                value: '3.3.4.2-more-options',
                label: 'More Options',
                childrenAsSubmenu: true,
                children: [
                  { value: '3.3.4.2.1', label: 'Option 3.3.4.2.1' },
                  { value: '3.3.4.2.2', label: 'Option 3.3.4.2.2' },
                ],
              },
              {
                value: '3.3.4.3',
                label: 'Option 3.3.4.3 (Group)',
                children: [
                  { value: '3.3.4.x.1', label: 'Option 3.3.4.x.1' },
                  { value: '3.3.4.x.2', label: 'Option 3.3.4.x.2' },
                  { value: '3.3.4.x.3', label: 'Option 3.3.4.x.3' },
                  { value: '3.3.4.x.4', label: 'Option 3.3.4.x.4' },
                ],
              },
            ],
          },
          {
            value: '3.3.5',
            label: 'Option 3.3.5 (Group)',
            children: [
              { value: '3.3.x.1', label: 'Option 3.3.x.1' },
              { value: '3.3.x.2', label: 'Option 3.3.x.2' },
              {
                value: '3.3.5.3 (Group)',
                label: 'Option 3.3.5.3',
                children: [
                  { value: '3.3.5.x.1', label: 'Option 3.3.5.x.1' },
                  { value: '3.3.5.x.2', label: 'Option 3.3.5.x.2' },
                  { value: '3.3.5.x.3', label: 'Option 3.3.5.x.3' },
                ],
              },
            ],
          },
        ],
      },
    ],
  },
  { value: 4, label: 'Option 4' },
] as const satisfies SelectOption[]

⚠️ Unsupported features for individual submenu

  • Create options dynamically: cannot create options in the currently focused opening submenu (i.e. all creating options are placed at root menu)
  • Persist sticky options when filtering: unlike in root menu, sticky options within submenu will be filtered out if not matched

Virtual Scroll

Virtual Scroll is enabled by default and cannot be opt-outed.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { HSelect, type SelectOption, type SelectValue } from '@holistics/design-system'

const options = Array.from(
  { length: 1000 },
  (_, i) => ({ value: `opt-${i + 1}`, label: `Option ${i + 1}` }),
) satisfies SelectOption[]

const value = ref<SelectValue>()
</script>

<template>
  <HSelect
    v-model="value"
    :options="options"
    class="w-80"
  />
</template>

API

Pass-through: <HPopper>

What does this mean?

All props, events, and attrs that are not specified in the tables below will be passed to the element/component described above.

Props

NameTypeDescription
options *
readonly SelectOption[]

An array of select options.

Option properties:

  • value [required]
  • label [required]
  • icon
  • disabled
  • class: HTML class
  • style: HTML style
  • children: [Group] list of options (no deep limitation)
  • childrenAsSubmenu: [Group] whether to render the children as a separate submenu
  • childrenSubmenuProps: [Group - Submenu] set submenu
  • initialExpanded: [Group] whether to expand at the first time opening the (sub)menu
  • action: action on click. Interface: (option: SelectOption) => (void | Promise<void>)
  • stickyTop: whether this option should stick to the top of the options panel and does not scroll.
  • stickyBottom: whether this option should stick to the bottom of the options panel and does not scroll.
modelValue 
SelectValue | SelectValue[]

Primitive value: string | number | boolean | null

or Array of primitive values if multiple is enabled.

icon 
"function" | "cancel" | "copy" | "cut" | "error" | "pause" | "play" | "add-block" | "add-circle" | "add-filter" | "add-tag" | "add-user" | "add" | "address-card" | "adhoc-query" | ... 466 more ...

Icon to display on the left side of the select.

iconSpin 
boolean

Whether the icon should spin.

placeholder 
string
= "Select..."

Placeholder text.

optionIndent 
number
= 20

[Tree] The distance (in px) between options of 2 continuous level.

disabled 
boolean
clearable 
boolean

If true, select can be cleared.

multiple 
SelectMultiple

If true, multiple values can be selected.

indeterminateKeys 
Key[]
checkable 
boolean

Whether to display the selection checkbox. When true, this will always be a multiple select.

NOTE: Due to performance reasons, Select with checkable = true will only work with options having value of type string | number. Other types (boolean, null) will lead to unexpected behavior.

checkCascade 
boolean

Whether to cascade checkboxes.

checkStrategy 
CheckStrategy
= "all"
showCheckmark 
boolean

Whether to show a check mark at the selected options.

filterable 
boolean

If true, options are filterable/searchable.

filterAdvancedSearch 
boolean

When true, enable hierarchical and stemming search.

filterIncludeDirectChildren 
boolean

Whether to also include all direct (first-level) children of matched groups when filtering/searching.

filterIncludeStickyOptions 
boolean

Whether to include Sticky Options when filtering/searching:

  • false: sticky options are always shown and are not filterable (can still be highlighted)
  • true: only matched sticky options are shown and highlighted
searchText 
string

Search text used for filtering option(s).

searchDebounce 
number
= 200

Debounce time for searching (ms).

searchLoading 
boolean

Whether the onSearch function is loading.

inputId 
string

The id of the input element.

preserveSearchText 
boolean

Whether to preseve search text when blurring, after selecting option, or when pressing Escape while menu isn't opened.

createable 
boolean

If true, new options can be created.

createFn 
CreateOptFn

An array of select options.

Interface: (value: SelectValue) => SelectOptionDefault

createAtTop 
boolean

When true, creating (i.e. unmatched option when searching) & created options will be put at the top of the filterable options list. The order is now reversed: Creating -> Created -> Original.

refreshFn 
RefreshFn

Function to refresh the options.

Interface: () => (void | Promise<void>)

refreshLoading 
boolean

Whether the onRefresh function is loading.

matchAnchorSize 
boolean | "min" | "max"
= true

Set the size of the options menu depending on the size of the select trigger element.

  • true: height/width of options menu = height/width of select trigger element.
  • false: the size of options menu will not depend on the size of select trigger element.
  • 'min': min-height/min-width of options menu = height/width of select trigger element.
  • 'max': max-height/max-width of options menu = height/width of select trigger element.
theme 
"normal" | "underline"
= "normal"

The theme of the select component.

inline 
boolean

If true, display the options menu as inlined instead of floating.

groupSelectable 
boolean

Whether to allow selecting group option (best when using with inline mode).

floatingClass 
HTMLAttributeClass

Custom class for the options menu.

maxHeight 
string
= "15.5rem"

Max height of the filterable options for scrolling.

scrollBottomLoading 
boolean

Whether the onScrollBottom function is loading.

preserveFocus 
boolean

Whether to preserve current focused element while the menu is opening and options is changed (e.g. infinite scroll).

Events

NameParametersDescription
@blur
[event: FocusEvent]
@focus
[event: FocusEvent]
@select
[value: SelectValue, option: SelectOption]
@update:modelValue
[value: SelectValue | SelectValue[] | undefined]
@update:indeterminateKeys
[keys: Key[]]
@update:searchText
[value: string]
@update:searchLoading
[loading: boolean]
@update:refreshLoading
[loading: boolean]
@update:scrollBottomLoading
[loading: boolean]
@deselect
[value: SelectValue, option: SelectOption]
@focusOption
[option?: SelectOption | undefined]
@search
SearchFn

Function triggered when the search text is changed. If this is specified, searching from options & createable won't work, the call site must handle options manually.

Interface: (searchText: string) => (void | Promise<void>)

@refresh
DefinePropEmit

Function to refresh the options.

Interface: () => (void | Promise<void>)

@scrollBottom
DefinePropEmit<[menuHolder?: SelectOption]>

Fired when scrolling to bottom of a menu. option = undefined when it's root menu.

Interface: (menuHolder?: SelectOption) => (void | Promise<void>)

Slots

NameScopedDescription
#placeholder
{ placeholder: string; }
#trigger-selected-options
{ selectedOptions: SelectOption[]; disabled?: boolean | undefined; multiple?: SelectMultiple | undefined; searchText: string; searchLoading?: boolean | undefined; ... 4 more ...; deselect: (option: SelectOption) => void; }
#option-prepend
{ option: SelectOption; }
#option
SelectOptionSlotProps
#options-empty
any
#options-filter-empty
any

Exposed

NameTypeDescription
focusAndOpen
() => void
close
(keepFocus?: boolean | undefined) => void
updatePositions
() => void
handleCheck
(option: SelectOption, assumeChecked?: boolean | undefined) => void

Check/uncheck a single option based on its current checked state (can be overridden by assumeChecked).

setCheckedStates
(checkedKeys: Key[] | null | undefined) => void

Calculate, then set correct checked and indeterminate keys based on a list of checked keys.