Rich Select Box (TRichSelect)

VueJs reactive rich select component inspired in jquery select2 with configurable classes, variants, and most common events. Friendly with utility-first frameworks like TailwindCSS.

Playground:


Props

PropertyTypeDefault valueDescription
idStringundefinedid attribute of the button used as the options toggler
disabledBooleanundefineddisabled attribute of the button used as the options toggler
nameStringundefinedname attribute of the button used as the options toggler
readonlyBooleanundefinedreadonly attribute of the button used as the options toggler
autofocusBooleanundefinedautofocus attribute of the button used as the options toggler
requiredBooleanundefinedrequired attribute of the button used as the options toggler
tabindex[String, Number]undefinedtabindex attribute of the button used as the options toggler
options[Array, Object][]The initial list of options in any of the valid formats accepted
valueAttributeStringundefinedAttribute from the options that should be used as the value of the selected option, accepts dot dotation
textAttributeStringundefinedAttribute from the options that should be used as the text of the selected option, accepts dot dotation
delayNumber250Time in milliseconds between after performs a search when fetching results from custom function
fetchOptionsFunctionundefinedMethod for fetching the options, receives the query as string and the nextPage if apply
minimumInputLengthNumberundefinedMinimum length of the search query to start filtering the results
minimumInputLengthText[Function, String]* (see below)The text that is shown before the user reaches the min length for trigger a query (if set)
minimumResultsForSearchNumberundefinedIf set, The minimum length of the options list needed to show the search box
value (v-model)[String, Number]nullThe current value of the component
hideSearchBoxBooleanfalseIf set will not show a search box
openOnFocusBooleantrueIf set will open the dropdown when the component is focused
closeOnSelectBooleantrueIf set will close the dropdown once an option is selected
selectOnCloseBooleanfalseIf set will select the highligted option when the dropdown is closed
clearableBooleanfalseIf set will show a close button to clear the value of the input
placeholderStringundefinedText that is being shown while no option selected
searchBoxPlaceholderString'Search...'Text that is being shown in the search box while empty
noResultsTextString'No results found'Text that is being shown when the search query doesn't return any result
searchingTextString'Searching...'Text that is being shown while the input is querying the results
loadingMoreResultsTextString'Loading more results...'Text that is being shown when the search box is loading more paginated results
maxHeight[String, Number]300Max height of the dropdown parsed as px, also accepts the height as string in any valid units
classesObject{...} (see below)The default CSS classes
fixedClassesObject{...} (see below)The default CSS Fixed classes shared for all variants
variantsObjectundefinedThe different variants of classes the component have
variant[String, Object]undefinedThe variant that should be used

* The minimumInputLengthText accepts an string or a Function that receive the min input length and the current query and should return an string, example:

(minimumInputLength: number, _query?: string) => {
  return 'Please enter' + minimumInputLength +'  or more characters';
}

Classes and variants format

This component expects an object with classes named after every child element.

The properties in that object are the following:

PropertyDescription
wrapperDiv that wraps the whole component
buttonWrapperDiv that wraps the button that is used as the input
selectButtonButton that is used as the input
selectButtonLabelDiv inside the button that holds the text of the selected option
selectButtonPlaceholderDiv that contains the placeholder when no value selected
selectButtonIconIcon with the chevron inside the button
selectButtonClearButtonButton for clear the selected value
selectButtonClearIconCross icon inside the clear button
dropdownDropdown wrapper
dropdownFeedbackSearching, min input length, and no results text
loadingMoreResultsLoading more results text in paginated search
optionsListThe list of options
searchWrapperDiv that wraps the search box
searchBoxThe search box input
optgroupThe div that wraps an optgroup
optionThe default option item
disabledOptionThe option item when its disabled 1.3.3+
highlightedOptionThe option item when its highlighted
selectedOptionThe option item when it's selected
selectedHighlightedOptionThe option item when its selected & highlighted
optionContentDiv that wraps the option text
optionLabelThe option text
selectedIconThe option checkmark icon
enterClassVue Custom Transition Class for the options dropdown
enterActiveClassVue Custom Transition Class for the options dropdown
enterToClassVue Custom Transition Class for the options dropdown
leaveClassVue Custom Transition Class for the options dropdown
leaveActiveClassVue Custom Transition Class for the options dropdown
leaveToClassVue Custom Transition Class for the options dropdown

Default fixed classes

As you may know, the fixed classes are shared and merged with the different variants and default classes. The classes we define here as default are the ones that you usually will need to make this component works correctly so you can only focus on colors, typography, etc when creating your theme.

{
  wrapper: 'relative',
  buttonWrapper: 'inline-block relative w-full',
  selectButton: 'w-full flex text-left justify-between items-center',
  selectButtonLabel: 'block truncate',
  selectButtonPlaceholder: 'block truncate',
  selectButtonIcon: 'fill-current flex-shrink-0 ml-1 h-4 w-4',
  selectButtonClearButton: 'flex flex-shrink-0 items-center justify-center absolute right-0 top-0 m-2 h-6 w-6',
  selectButtonClearIcon: 'fill-current h-3 w-3',
  dropdown: 'absolute w-full z-10',
  dropdownFeedback: '',
  loadingMoreResults: '',
  optionsList: 'overflow-auto',
  searchWrapper: 'inline-block w-full',
  searchBox: 'inline-block w-full',
  optgroup: '',
  option: 'cursor-pointer',
  disabledOption: 'opacity-50 cursor-not-allowed',
  highlightedOption: 'cursor-pointer',
  selectedOption: 'cursor-pointer',
  selectedHighlightedOption: 'cursor-pointer',
  optionContent: '',
  optionLabel: 'truncate block',
  selectedIcon: 'fill-current h-4 w-4',
  enterClass: '',
  enterActiveClass: '',
  enterToClass: '',
  leaveClass: '',
  leaveActiveClass: '',
  leaveToClass: '',
};

Default classes

{
  wrapper: '',
  buttonWrapper: '',
  selectButton: 'border bg-white rounded p-2 focus:outline-none focus:shadow-outline',
  selectButtonLabel: '',
  selectButtonPlaceholder: 'text-gray-500',
  selectButtonIcon: '',
  selectButtonClearButton: 'hover:bg-gray-200 text-gray-500 rounded',
  selectButtonClearIcon: '',
  dropdown: 'rounded bg-white shadow',
  dropdownFeedback: 'text-sm text-gray-500',
  loadingMoreResults: 'text-sm text-gray-500',
  optionsList: '',
  searchWrapper: 'bg-white p-2',
  searchBox: 'p-2 bg-gray-200 text-sm rounded border focus:outline-none focus:shadow-outline',
  optgroup: 'text-gray-500 uppercase text-xs py-1 px-2 font-semibold',
  option: '',
  disabledOption: '',
  highlightedOption: 'bg-gray-300',
  selectedOption: 'font-semibold bg-gray-100',
  selectedHighlightedOption: 'bg-gray-300 font-semibold',
  optionContent: 'flex justify-between items-center p-2',
  optionLabel: 'truncate block',
  selectedIcon: '',
  enterClass: '',
  enterActiveClass: 'opacity-0 transition ease-out duration-100',
  enterToClass: 'opacity-100',
  leaveClass: 'transition ease-in opacity-100',
  leaveActiveClass: '',
  leaveToClass: 'opacity-0 duration-75',
}

Theme Example

{
  // Remember that these classes are merged with the fixed classes
  classes: {
    wrapper: '',
    buttonWrapper: '',
    selectButton: 'border bg-white rounded p-2 focus:outline-none focus:shadow-outline',
    selectButtonLabel: '',
    selectButtonPlaceholder: 'text-gray-500',
    selectButtonIcon: '',
    selectButtonClearButton: 'hover:bg-gray-200 text-gray-500 rounded',
    selectButtonClearIcon: '',
    dropdown: 'rounded bg-white shadow',
    dropdownFeedback: 'text-sm text-gray-500',
    loadingMoreResults: 'text-sm text-gray-500',
    optionsList: '',
    searchWrapper: 'bg-white p-2',
    searchBox: 'p-2 bg-gray-200 text-sm rounded border focus:outline-none focus:shadow-outline',
    optgroup: 'text-gray-500 uppercase text-xs py-1 px-2 font-semibold',
    option: '',
    highlightedOption: 'bg-gray-300',
    selectedOption: 'font-semibold bg-gray-100',
    selectedHighlightedOption: 'bg-gray-300 font-semibold',
    optionContent: 'flex justify-between items-center p-2 cursor-pointer',
    optionLabel: 'truncate block',
    selectedIcon: '',
    enterClass: '',
    enterActiveClass: 'opacity-0 transition ease-out duration-100',
    enterToClass: 'opacity-100',
    leaveClass: 'transition ease-in opacity-100',
    leaveActiveClass: '',
    leaveToClass: 'opacity-0 duration-75',
  },
  variants: {
    danger: {
      // As explained in the "Theming" sections we only add the classes we want to override
      selectButton: 'border border-red-500 text-red-500 bg-red-100 rounded p-2 focus:outline-none focus:shadow-outline',
      selectButtonPlaceholder: 'text-red-400',
      selectButtonClearButton: 'hover:bg-red-200 text-red-500 rounded',
    },
    //... More variants
  }
}

Options format

This component accepts the options in the same format as the TSelect component. See TSelect options format for more info.

Events

EventArgumentsDescription
inputString (The current value of the select)Emitted every time the value of the v-model change
changeString (The current value of the select)Emitted when the select is blurred and the value was changed since it was focused
focusFocusEventEmitted when the select is focused
blurFocusEventEmitted when the select is blurred
fetch-errorErrorEmitted when the fetchOptions function returns an error
clickMouseEventWhen the button uses as input is clicked

Scoped slots

SlotDescription
labelSelected option label inside the button
optionOption content inside the list of options
searchingTextTo place instead of the default searching text feedback
noResultsTo place instead of the no results text feedback
loadingMoreResultsTextTo place instead of the loading more results text
dropdownUpTo place content above the list of options
dropdownDownTo place content below the list of options

Label slot

Allows you to change the template of the label, it contains the following data:

SlottypeDescription
queryStringThe current search query
optionObjectAnd object with the text and value attribute together with a raw attribute that contains the original option value before normalized
classNameStringThe selectedButtonLabel class in case you can to re-apply it

Example:

Consider that the options come from an ajax query that contains a repo object with info of a GitHub repository

<template>
  <t-rich-select
    :fetch-options="fetchOptions"
    placeholder="select an option"
    value-attribute="full_name"
    text-attribute="full_name"
    :minimum-input-length="1"
  >
    <template
      slot="label"
      slot-scope="{ className, option, query }"
    >
      <div class="flex">
        <span class="flex-shrink-0">
          <img
            class="w-10 h-10 rounded-full"
            :src="option.raw.owner.avatar_url"
          >
        </span>
        <div class="flex flex-col ml-2 text-gray-800">
          <strong>{{ option.raw.full_name }}</strong>
          <span class="text-sm leading-tight text-gray-700">{{ option.raw.description }}</span>
        </div>
      </div>
    </template>
  </t-rich-select>
</template>

<script>
export default {
  methods: {
    fetchOptions (q) {
      return fetch(`https://api.github.com/search/repositories?q=${q}&type=public`)
        .then((response) => response.json())
        .then((data) => ({ results: data.items }))
    }
  }
}
</script>

The example above will look like this: (search and select a repository):

Option slot

Allows you to replace the option content inside the list of options

SlottypeDescription
indexNumberThe index of the option
isHighlightedBooleanIf the option is highlighted
isSelectedBooleanIf the option is selected
optionObjectAnd object with the text and value attribute together with a raw attribute that contains the original option value before normalized
queryStringThe current search query
classNameStringThe optionContent class in case you can to re-apply it

Example:

Consider that the options come from an ajax query that contains a repo object with info of a GitHub repository

<template>
  <t-rich-select
    :fetch-options="fetchOptions"
    placeholder="select an option"
    value-attribute="full_name"
    text-attribute="full_name"
    :minimum-input-length="1"
  >
    <template
      slot="option"
      slot-scope="{ index, isHighlighted, isSelected, className, option, query }"
    >
      <div :class="className">
        <span class="flex-shrink-0">
          <img
            class="w-10 h-10 rounded-full"
            :src="option.raw.owner.avatar_url"
          >
        </span>
        <div class="flex flex-col ml-2 text-gray-800">
          <strong>
            {{ option.raw.full_name }}
            <span v-if="isSelected">(Selected)</span>
          </strong>
          <span class="text-sm leading-tight text-gray-700">{{ option.raw.description }}</span>
        </div>
      </div>
    </template>
  </t-rich-select>
</template>

<script>
export default {
  methods: {
    fetchOptions (q) {
      return fetch(`https://api.github.com/search/repositories?q=${q}&type=public`)
        .then((response) => response.json())
        .then((data) => ({ results: data.items }))
    }
  }
}
</script>

The example above will look like this: (search for a repository):

searchingText slot

Allows you to replace the "searching" text feedback

SlottypeDescription
queryStringThe current search query
textStringThe original searching text
classNameStringThe dropdownFeedback class in case you can to re-apply it

Example:

<t-rich-select>
  <template
    slot="searchingText"
    slot-scope="{ query, text, className }"
  >
    <div :class="className">Please hold on we are looking for options with the query "{{ query }}".</div>
  </template>
</t-rich-select>

noResults slot

Allows you to replace the "no results" text feedback

SlottypeDescription
queryStringThe current search query
textStringThe original searching text
classNameStringThe dropdownFeedback class in case you can to re-apply it

Example:

<t-rich-select>
  <template
    slot="noResults"
    slot-scope="{ query, text, className }"
  >
    <div :class="className">Sorry your "{{ query }}" didnt return any value</div>
  </template>
</t-rich-select>

loadingMoreResultsText slot

Allows you to replace the "loading more results" text feedback below the options

SlottypeDescription
queryStringThe current search query
textStringThe original searching text
classNameStringThe dropdownFeedback class in case you can to re-apply it
nextPageNumber,undefinedThe next page to be loaded

Example:

<t-rich-select>
  <template
    slot="noResults"
    slot-scope="{ query, text, className }"
  >
    <div :class="className">Sorry your "{{ query }}" didnt return any value</div>
  </template>
</t-rich-select>

Allows you to place content above the list of options

SlottypeDescription
queryStringThe current search query
selectedOptionObjectAnd object with the text and value attribute together with a raw attribute that contains the original option value before normalized
optionsArrayThe full list of options (filtered)

Example:

Consider that the options come from an ajax query that contains a repo object with info of a GitHub repository

<t-rich-select>
  <template
    slot="dropdownUp"
    slot-scope="{ query, selectedOption, options }"
  >
    <div>You have {{ options.length }} options</div>
  </template>
</t-rich-select>

Allows you to place content below the list of options

SlottypeDescription
queryStringThe current search query
selectedOptionObjectAnd object with the text and value attribute together with a raw attribute that contains the original option value before normalized
optionsArrayThe full list of options (filtered)

Example:

Useful for place an action button, consider this example for dinamically create an option:

<template>
  <t-rich-select
    v-model="selected"
    :options="options"
  >
    <template
      slot="dropdownDown"
      slot-scope="{ query, selectedOption, options }"
    >
      <div
        v-if="query"
        class="text-center"
      >
        <button
          type="button"
          class="block w-full p-3 text-white bg-blue-500 border hover:bg-blue-600"
          @click="createOption(query)"
        >
          Create {{ query }}
        </button>
      </div>
    </template>
  </t-rich-select>
</template>

<script>
export default {
  data () {
    return {
      selected: null,
      options: ['Option 1', 'Option 2']
    }
  },
  methods: {
    createOption (text) {
      this.options.push(text)
      this.selected = text
    }
  }
}
</script>


The example above will look like this: (search for an option)