Multi Select Combobox LWC component is easy to create and a generic component because it works for both single select and multi select with search capability. You can use this component in a multi-way like search combobox/picklist or multi select combobox with searchability. As a result, multi-use with lesser code.
Lightning & LWC comes with many pre-built components that are easy to use but still many components are missing by date. One of them is the multi-select Combobox/picklist component or search combobox. We will create this within a single component without using any child component. You can find the complete source code in this post.
Multi Select Combobox component Features:
- Single component
- Options attribute to pass List
- Boolean variable to switch between Single Select and Multi Select.
- Separate attributes for single and multi select to get selected values.
- search functionality to filter options
- combobox disable functionality
- Option to set the minimum character to start searching
- Ability to set the label of combobox
Demo GIF:
Single Select Combobox:
Multi Select Combobox:
Step-1: Create the multiSelectCombobox LWC Component
For creating a new lightning web component you need to use an IDE like VS Code.
Now after component creation, create the CSS file too. Now we will write markup for our component. I have used standard (Salesforce Lightning Design System) SLDS here for styling.
Markup for multiSelectCombobox.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<!-- Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header --> <template> <!-- Header Label --> <template if:true={label}> <label class="slds-form-element__label">{label}</label> </template> <div class="slds-combobox_container"> <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aria-expanded="true" aria-haspopup="listbox" role="combobox"> <!-- Search Input --> <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none"> <lightning-input disabled={disabled} class="inputBox" placeholder="Select an Option" onblur={blurEvent} onclick={showOptions} onkeyup={filterOptions} value={searchString} auto-complete="off" variant="label-hidden" id="combobox-id-1" ></lightning-input> <lightning-icon class="slds-input__icon" icon-name="utility:down" size="x-small" alternative-text="search"></lightning-icon> </div> <!-- Dropdown List --> <template if:true={showDropdown}> <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid"><!--style="{! 'max-height:' + (8 + (v.recordCount * 40)) + 'px' }""--> <ul class="slds-listbox slds-listbox_vertical recordListBox" role="presentation"> <template if:false={message} > <template for:each={optionData} for:item="option"> <template if:true={option.isVisible}> <li key={option.value} data-id={option.value} onmousedown={selectItem} class="slds-listbox__item eachItem"> <template if:true={option.selected}> <lightning-icon icon-name="utility:check" size="x-small" alternative-text="icon" ></lightning-icon> </template> <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{option.label}</span> </li> </template> </template> </template> <template if:true={message} > <li class="slds-listbox__item"> <span class="slds-media slds-listbox__option_entity verticalAlign slds-truncate">{message}</span> </li> </template> </ul> </div> </template> </div> </div> <!-- Multi Select Pills --> <template for:each={optionData} for:item="option"> <template if:true={option.selected}> <lightning-pill key={option.value} class="slds-m-around_xx-small" name={option.value} label={option.label} onremove={removePill}></lightning-pill> </template> </template> </template> <!-- Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE --> |
Now open the controller file multiSelectCombobox.js and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
/* Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header */ import { LightningElement, track, api } from 'lwc'; export default class MultiSelectCombobox extends LightningElement { @api options; @api selectedValue; @api selectedValues = []; @api label; @api minChar = 2; @api disabled = false; @api multiSelect = false; @track value; @track values = []; @track optionData; @track searchString; @track message; @track showDropdown = false; connectedCallback() { this.showDropdown = false; var optionData = this.options ? (JSON.parse(JSON.stringify(this.options))) : null; var value = this.selectedValue ? (JSON.parse(JSON.stringify(this.selectedValue))) : null; var values = this.selectedValues ? (JSON.parse(JSON.stringify(this.selectedValues))) : null; if(value || values) { var searchString; var count = 0; for(var i = 0; i < optionData.length; i++) { if(this.multiSelect) { if(values.includes(optionData[i].value)) { optionData[i].selected = true; count++; } } else { if(optionData[i].value == value) { searchString = optionData[i].label; } } } if(this.multiSelect) this.searchString = count + ' Option(s) Selected'; else this.searchString = searchString; } this.value = value; this.values = values; this.optionData = optionData; } filterOptions(event) { this.searchString = event.target.value; if( this.searchString && this.searchString.length > 0 ) { this.message = ''; if(this.searchString.length >= this.minChar) { var flag = true; for(var i = 0; i < this.optionData.length; i++) { if(this.optionData[i].label.toLowerCase().trim().startsWith(this.searchString.toLowerCase().trim())) { this.optionData[i].isVisible = true; flag = false; } else { this.optionData[i].isVisible = false; } } if(flag) { this.message = "No results found for '" + this.searchString + "'"; } } this.showDropdown = true; } else { this.showDropdown = false; } } selectItem(event) { var selectedVal = event.currentTarget.dataset.id; if(selectedVal) { var count = 0; var options = JSON.parse(JSON.stringify(this.optionData)); for(var i = 0; i < options.length; i++) { if(options[i].value === selectedVal) { if(this.multiSelect) { if(this.values.includes(options[i].value)) { this.values.splice(this.values.indexOf(options[i].value), 1); } else { this.values.push(options[i].value); } options[i].selected = options[i].selected ? false : true; } else { this.value = options[i].value; this.searchString = options[i].label; } } if(options[i].selected) { count++; } } this.optionData = options; if(this.multiSelect) this.searchString = count + ' Option(s) Selected'; if(this.multiSelect) event.preventDefault(); else this.showDropdown = false; } } showOptions() { if(this.disabled == false && this.options) { this.message = ''; this.searchString = ''; var options = JSON.parse(JSON.stringify(this.optionData)); for(var i = 0; i < options.length; i++) { options[i].isVisible = true; } if(options.length > 0) { this.showDropdown = true; } this.optionData = options; } } removePill(event) { var value = event.currentTarget.name; var count = 0; var options = JSON.parse(JSON.stringify(this.optionData)); for(var i = 0; i < options.length; i++) { if(options[i].value === value) { options[i].selected = false; this.values.splice(this.values.indexOf(options[i].value), 1); } if(options[i].selected) { count++; } } this.optionData = options; if(this.multiSelect) this.searchString = count + ' Option(s) Selected'; } blurEvent() { var previousLabel; var count = 0; for(var i = 0; i < this.optionData.length; i++) { if(this.optionData[i].value === this.value) { previousLabel = this.optionData[i].label; } if(this.optionData[i].selected) { count++; } } if(this.multiSelect) this.searchString = count + ' Option(s) Selected'; else this.searchString = previousLabel; this.showDropdown = false; this.dispatchEvent(new CustomEvent('select', { detail: { 'payloadType' : 'multi-select', 'payload' : { 'value' : this.value, 'values' : this.values } } })); } } /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Finally, paste the CSS in the multiSelectCombobox.css file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
.verticalAlign { cursor: pointer; padding: 0px 5px !important; } .slds-dropdown { padding:0px !important; } .recordListBox { margin-top:0px !important; overflow-y: scroll; } .slds-listbox li { padding: .45rem 0.7rem !important; display: flex; } .inputBox input { padding-left: 10px; } .eachItem:hover { background-color: #F1F1F1; cursor: pointer; } /* For Scrolling */ ::-webkit-scrollbar { width: 7px; height: 7px; } ::-webkit-scrollbar-track { display: none !important; } ::-webkit-scrollbar-thumb { border-radius: 10px; background: rgba(0,0,0,0.4); } |
Step-2: Now we will call the component.
For Single Select:
1 2 3 4 5 6 7 8 9 10 11 |
<aura:application extends="force:slds"> <aura:attribute name="options" type="List" default="[{'label':'Bob','value':'123'}, {'label':'Chrissey','value':'345'}, {'label':'Jessica','value':'456','disabled': true}, {'label':'Sunny','value':'567'}]" /> <aura:attribute name="selectedValue" type="String" default="" description="Selected value in single Select" /> <c:multiSelectCombobox options="{!v.options}" selectedValue="{!v.selectedValue}" label="Single Select Combobox"></c:multiSelectCombobox> </aura:application> |
For Multi Select:
1 2 3 4 5 6 7 8 9 10 11 |
<aura:application extends="force:slds"> <aura:attribute name="options" type="List" default="[{'label':'Bob','value':'123'}, {'label':'Chrissey','value':'345'}, {'label':'Jessica','value':'456','disabled': true}, {'label':'Sunny','value':'567'}]" /> <aura:attribute name="selectedValues" type="List" default="" description="Selected value in Multi Select" /> <c:multiSelectCombobox multiSelect="true" options="{!v.options}" selectedValues="{!v.selectedValues}" label="Multi Select Combobox"></c:multiSelectCombobox> </aura:application> |
Finally, our component is ready to use. Now while calling this component, you need to pass the options list and attribute in which the selected value will be stored. You can also set the additional attributes and switch between single and multi select component. You can also get selected data in the component through the event and attributes as well. When you select a value and click outside of the box, an event is fired.
How to pass the data (Option list format):
Data should be passed as Object List in the form of label and value.
Eg: [{‘label’: ‘University of Texas’, ‘value’:’UTT112′}]
How to get the Selected Values:
To get the values, handle the onselect event in your parent component. You will get data inside the event handler.
1 2 3 |
<c:multiSelectCombobox onselect="{!c.handleSelect}" multiSelect="true" options="{!v.options}" selectedValues="{!v.selectedValues}" ></c:multiSelectCombobox> |
1 2 3 4 5 6 7 8 9 10 |
handleSelect : function(component, event, helper) { var payload = event.getParam('payload'); var payloadType = event.getParam('payloadType'); if(payloadType === 'multi-select') { console.log(payload.value); console.log(payload.values); } } |
To get the SLDS Reference Click Here
Also Check:
For any queries or suggestions regarding this post, comment below:
Cheers … 🙂
How would you pass a dynamic list of options to the combobox in LWC? In my js, I have:
And in my html, I have:
The console output is the following, so I think I’m building the array correctly. But the combobox doesn’t contain any values.
On Your Feet
Keep the Pace
Let’s Go!
Outdoor: No Sweat
Outdoor: Spirited
Outdoor: Challenging
Choose Your Pace
Outdoor: Choose Your Pace
Easy
Active
Challenging
Moderate
Moderately Challenging
None
Hi Brian,
Thanks for your query. It might be happening because your component is loading first and options are bound to it later when you received data from backend. Easy way to resolve this is to refresh your component options again. Just add the below code in your JS file
and now call this API function from your parent component after you have received data from Apex.
Now this will refresh the list and dropdown will be visible.
Works great, thank you!
Awesome…. It’s my Pleasure?
Getting [this.template.querySelector(…).refreshOptions is not a function]
after loading the component , in parent component where do call this?
Hi Veeranjineyulu,
You need to add a small timeout of 20 ms. Put this code in your timeout methout. By the time your code is executed. HTML body is not created so this error is coming.
Hi,
When the component is invoked from another LWC, the ‘value’ and ‘values’ in “connectedCallback” function are null.
Parent LWC –
In JS file – I have just declared as – @track selvalues = [];
I get an error saying – ” [Cannot read property ‘includes’ of null]”
Upon debugging I found out that, when the component loads in ‘connectedCallback” function the values are null.
Please suggest!
Hi Karuna,
This is because your multiSelectCombobox is loading first and you are passing data to it later when you are getting it. To solve this issue, create a @api function in multiSelectCombobox and pass data to it from Parent, it will reset the values.
API Function –
Call this way in Parent after you are setting data in selValues attribute.
Hi Suyash,
Thanks for your reply!
Could you please also tell me how to set the ‘selValues’ in my parent LWC. Currently I only have it declared as ‘@track’ variable. When do I set the ‘selValues’ variable? Is it when I populate the picklistOptions for the combo box?
Appreciate if you could let me know these details.
Hi Suyash,
Please ignore my previous reply. This works good and very helpful. Great work 🙂
Thanks for the Appreciation Karuna 🙂
Hi, I am getting an error message in Console:
TypeError: Cannot read property insertBefore of undefined.
I am using Parent –Child –Child (Your component) in the code.
Can you try to share the exact line where this error is coming from. Click on the component name link right to your console log.
Hi,
I am getting an error when I preview the host Aura App.
error: This page has an error. You might just need to refresh it.
Error during LWC component connect phase: [Unknown public property “autoComplete” of element <LIGHTNING-INPUT>. This is likely a typo on the corresponding attribute “auto-complete”.]
Failing descriptor: {c:multiSelectCombobox}
I followed the same steps. can you please tell me why am i getting this?
Hi Subhadeep,
Just remove the auto-complete=”off” attribute from the lightning-input tag on line 13 in the HTML file.
how can get apex code for this
Hi Abdul,
Apex code is already present in the Post
Hi, How would you reset the filters in this scenario? I tried the solution, but receiving proxy issue.
Hi Arjun,
I am unable to get this. Do you mean resetting the input box?
How can you reset multi select from parent element? I tried query selector and then tried refresh() and it did not do any changes
Hi Sai,
For resetting the values, you need to iterate through options attribute and make .selected attribute false.
Hi Suyash,
Your multi-select component is great to use. Thanks for this.
I have one doubt though. I am using your lwc component inside another lwc component. I cannot able to get selected data in parent lwc component. I am new in lwc development, Can you please guide me?
Hi Sukanta,
You need to fire an event from the child component and handle that in the Parent component.
Thanks Suyash for your suggestion.
I am facing one more problem, when we are removing the pills and doing any operation previously selected options are still there. onselect event is not fired after clicking on pills close icon. Can you suggest any solution?
Hi Suyash,
I have a requirement to create Multiple dropdowns (ReUsable Component). Can you help me?
Hi Avinash,
You can call this component even multiple times on one page.
Hi Suyash,
I’m getting the values from apex controller dynamically.
Scenario:
I have picklist value like below
Label : Test 123
Value: 123
When I click on the input filed it shows the values drop down. I select ine of the value and it updates the input field with correct label.
But suppose I now edit the selected input text eg. “Test 123” to just “Test”. The dropdown updates and shows the filetred value. Now if I select the picklist option again, the Input field label is not getting set. It remains “Test”.
Somehow when I edit the selected value and try to select any new value after edit the input field is not getting updated
Hi, The vertical scroll bar is not working for the component. When we click on scroll bar the, the dropdown is disappearing. Can you please help on this
Hi Anjana,
This is because when you try to scroll by clicking, your combobox blur method will run and close the dropdown. In order to see the complete list, you can do the following:
[1] Add this attribute on your combobox – onopen={handlePayModeOpen}
[2] Add this method in JS
handlePayModeOpen() {
setTimeout(() => {
const topDiv = this.template.querySelector(‘[data-id=”scrollSec”]’);
topDiv.scrollTop = topDiv.scrollHeight;
}, 20);
}
[3] Add this attribute on div where the scroll is coming/scroll class is added – data-id=“scrollSec”
does this component support the selected options by default like preselected options?
Hi,
You can use the value and values attribute for passing pre-selected values.