Skip to content

Select

Usage

Select allows users to make a single selection or multiple selections from a list of options. This component is used both as Text select and Number select.

Types

The component Select comes in 2 types

  1. Select: A standard dropdown selection
  2. Select (with search): A dropdown selection with a search functionality.

Form Label (compact) in Select component can be placed either Label Up or Label Left, creating two variations of component usage.

Anatomy and Behavior

  1. Container - Contains placeholder when empty, and the selected option after user made the selection. The whole area is clickable to display options menu.

  2. Label - Is a Form label (compact) component.

  3. Placeholder, Keyword search or Selected option/s display - Selected option/s is/are displayed after user made selection. If not, a helpful placeholder (that contains an example input or informative, helping text regarding the input requested) or a keyword search (that indicates that the select component has search feature) is displayed. If the contents of the placeholder or selected option are longer than the width of the container, the text will get truncated. For Select (with search), the container becomes a text input field. As the user starts typing, the dropdown filters and shows only the filtered options. The filtering happens by sub-string. Use Select (with search) only when necessary in dropdowns where there are too many options.

  4. Arrow - Is a nested Icon component (chevron).

  5. Dropdown menu - The general rule of thumb is that only 6 to 10 Options should be visible in the Dropdown menu. The rest would be visible when the user scrolls up or down with a scrollbar.

  6. Option - Is one of the options in the Dropdown menu. If the contents of any Option are longer than the width of the container, then the text will get truncated and a Title attribute (a native HTML element like Tooltip) will display user the full text that is being hidden.

  7. Scrollbar - Is a Scrollbar (tiny) component nested inside Select component. Scrollbar is used only when we have more options than those visible in in a dropdown menu.

  8. Divider - is an optional element in the dropdown menu to divide Options in groups, for example to visually separate disabled Options.

  9. Action button - is a nesting Icon Button (compact) component and it is an optional feature for each individual Option in the Dropdown menu of the component. Any action can be assigned to it in project level and the icon should represent that action.

Behavior

  • Pressing Tab will trigger :focus state of the component, pressing Enter when focused will open the Dropdown menu and using Arrow keys will browse through the Options in the Dropdown menu. Pressing Enter while an option is highlighted will select the highlighted Option and close the Dropdown.
  • Pressing Tab again when a Select component is focused, will navigate and focus the next component in the UI.
  • Pressing Shift+Tab while browsing the Options will focus the container of the Select component back again.

States

Select can be Open or Closed and the Container has :hover, :active, :focus, Disabled states. Option within the Dropdown menu can be :hover, :active, :focus, Disabled and Selected.

Gaps and Sizes

  • Container width: Fill the parent container it is in

  • Container height: XL+ (Fixed)

  • Gap between container and dropdown menu: 2XS

  • Arrow width and height: M

  • Padding to scrollbar: 2XS

  • Content center aligned

  • Arrow is right aligned

  • Gap between contents (Placeholder / selected option and Arrow): max. space between, min. S

  • Container padding: S

  • Option width: Same as dropdown menu

  • Option height: L+

  • Option Internal vertical padding: XS

  • Option Internal horizontal padding: S

  • Divider width: Fill container

  • Divider height: Unit

  • Divider vertical padding: XS

  • Divider horizontal padding: S

Recommendations for Designers Minimum container width: 5XL

Tokens

SCSS Variable
Value
$select-bgColor
--color-grey-93
$select-placeholder-color
--color-grey-70
$select-placeholder-color--locked
--color-grey-80
$select-selected-color
--color-grey-25
$select-arrow-color
--bds-brand-primary-color
$select-arrow-color--locked
--color-grey-70
$select-outline-color--focus
--color-azure-45
$select-outline-color--locked
--color-grey-86
$select-dropdown-bgColor
--color-grey-93
$select-option-color
--color-grey-25
$select-option-color--locked
--color-grey-70
$select-option-bgColor--locked
--color-grey-90
$select-option-bgColor--hover
--color-grey-90
$select-option-bgColor--selected
--primary-bgColor-selected
$select-divider-color
--color-grey-80
$select-boxShadow
--debossed-1--grey-95
$select-boxShadow--hover
--debossed-2--grey-95
$select-boxShadow--locked
none
$select-dropdown-boxShadow
--elevation-4
$select-padding
--gutter-s

Properties

ts
export interface SelectGenericProps<T extends string | number> extends FormLabelOptionalProps {
  modelValue: T | null;
  options: SelectOptionGeneric<T>[];
  placeholder?: string;
  disabled?: boolean;
  id?: string;
  icon?: IconName;
  action?: SelectActionGeneric<T>;
  // if set to 0, all options will be shown, default: 0
  noOfOptionsToShow?: number;
  // If true, disabled options will be grouped and shown at the end of the list
  groupDisabledOptions?: boolean;
  sortOptions?: SortOptions<'alphabetical' | 'numerical'>;
  searchOptions?: SearchOptions<T>;
}

export interface SortOptions<T extends 'alphabetical' | 'numerical'> {
  type: T;
  order?: 'asc' | 'ascending' | 'desc' | 'descending';
  target?: 'text' | 'value';
  ignoreCase?: T extends 'alphabetical' ? boolean : never;
}

export interface SearchOptions<T extends number | string> {
  enableSearch: boolean;
  caseSensitive?: boolean;
  placeholder?: string;
  searchFunction?: (options: SelectOptionGeneric<T>[]) => SelectOptionGeneric<T>[];
}
ts
export interface SelectOptionGeneric<T extends string | number> {
  text: string | number,
  value: T | null;
  isDisabled?: boolean;
  hasAction?: boolean;
  icon?: IconName;
  /**
  * If true, the optional search of the Select component will ignore
  * this option and will always show it in the list.
  */
  ignoreSearch?: boolean;
}
ts
export interface SelectActionGeneric<T extends string | number> {
  text?: string;
  icon: IconName;
  function: (option: SelectOptionGeneric<T>) => Promise<void>;
}

WARNING

The following are deprecated!

ts
export type NumberSelectProps = SelectGenericProps<number>;
ts
export type TextSelectProps = SelectGenericProps<string>
ts
export type NumberSelectOption = SelectOptionGeneric<number>;
ts
export type TextSelectOption = SelectOptionGeneric<string>;

ModelValues

For more information on ModelValues please have a look to the Vue's Data Binding section.

vue
const searchString = defineModel('searchString', { default: '' });

Developer notes

Select defines an additional model searchString which empowers you to pass (and control) the search string from outside of the component.

vue
<Select
  v-model="newModelValue"
  v-model:searchString="data.mySearchString"
  :action="selectAction"
  v-bind="{
    ...pg,
    options: data.alphabeticalOptions,
    searchOptions: {
      ...pg.searchOptions,
      searchFunction: (options: SelectOptionGeneric<string>[]) =>
      options.filter((option) => option.text.toString().toLowerCase().includes(data.mySearchString.toLowerCase()))
    },
    label: 'Select with text',
    icon: pg.hasIcon ? pg.icon as IconName : undefined,
    sortOptions: data.sort ? pg.sortOptions : undefined
  }" 
/>

NumberSelect provides the following bindings to its slot

vue
<div v-for="(option, i) in group"
  :key="option.value || 'null'"
  class="item"
  data-is-select-item="true"
  :class="{
    selected: modelValue === option.value,
    disabled: option.isDisabled,
  }"
>
  <div class="item__content" @click="onSelect(option.value)">
    <slot v-bind="{ ...option, index: i }">{{ option.text }}</slot>
    //option is of type SelectOptionGeneric<number>
  </div>
</div>

TextSelect transforms these bindings slightly and binds the following to its slot

vue
<template #default="bind">
  <slot
    name="default"
    v-bind="{
      ...bind,
      value: bind.value && bind.value > 1 ? getModelValue(bind.value) : '',
    }"
  >{{ bind.text }}</slot>
</template>

How the action button should be implemented on the vue components:

vue
<script>
    // On the options must be declared the boolean hasAction as true if the option has an action.
  options =
  [ 
    { "value": 0, "text": "", "isDisabled": false }, 
    { "value": 1, "text": "Option 1", "isDisabled": false, "hasAction": true }, 
    { "value": 4, "text": "Option 4", "isDisabled": false, "hasAction": true }, 
    { "value": 7, "text": "Option 7", "isDisabled": false }
  ]


  // Declare actions function

  const selectAction: SelectActionGeneric<number | string> = ref({
    text: 'Action',
    icon: 'trash' as IconName,
    function: async (option: SelectOptionGeneric<number | string>) => {
      console.log('Action', option);
    },
  }).value;

  // The same function will be executed for all the options on the list that have an action button.

</script>



<template>
<!-- Assign the declared function to the prop "action" to be executed when a button is clicked.  -->

  <NumberSelect
    :action="selectAction"
    v-bind: options

  ></NumberSelect>

</template>