Building forms in Salesforce often requires users to relate multiple records together. While standard lookup fields are great for a single 1-to-1 relationship, they fall short when you need to select multiple items.
In this guide, we will build a reusable custom multi-select lookup component. We will connect an LWC to an Apex controller to fetch live search results and manage the user's selections directly in the UI.
Step 1: Create the Apex Controller
Before the LWC can search for records, we need a backend Apex class to query the database. This class must use the @AuraEnabled(cacheable=true) annotation so the LWC can call it efficiently.
public with sharing class CustomLookupController {
@AuraEnabled(cacheable=true)
public static List<SObject> searchRecords(String searchTerm) {
// Prevent empty queries from returning the whole database
if (String.isBlank(searchTerm)) {
return new List<SObject>();
}
// Example: Searching Accounts based on the user's text
String searchKey = '%' + searchTerm + '%';
return [SELECT Id, Name FROM Account WHERE Name LIKE :searchKey LIMIT 5];
}
}
Step 2: Scaffold the LWC Component
Open your terminal (or VS Code) and use the Salesforce CLI to generate the component:
sf lightning generate component -n multiSelectLookup -d force-app/main/default/lwc
Step 3: Build the HTML Template
Open multiSelectLookup.html. Our UI needs three things: a search input, a dropdown list for the search results, and a visual list (like "pills" or tags) to show the user what they have already selected.
<template>
<div class="lookup-container">
<!-- Search Input -->
<lightning-input type="search" label="Search Accounts" onchange={handleSearch}></lightning-input>
<!-- Search Results Dropdown -->
<template lwc:if={lookupResults.length}>
<ul class="lookup-results">
<template for:each={lookupResults} for:item="result">
<li key={result.Id} data-id={result.Id} data-name={result.Name} onclick={handleSelection}>
{result.Name}
</li>
</template>
</ul>
</template>
<!-- Selected Items (Pills) -->
<ul class="selected-values">
<template for:each={selectedValues} for:item="value">
<li key={value.Id}>
{value.Name}
</li>
</template>
</ul>
</div>
</template>
Step 4: The JavaScript Logic
Open multiSelectLookup.js. We will import the Apex method and handle the user's input and clicks.
import { LightningElement, track } from 'lwc';
import searchRecords from '@salesforce/apex/CustomLookupController.searchRecords';
export default class MultiSelectLookup extends LightningElement {
@track lookupResults = [];
@track selectedValues = [];
// Fires when the user types in the search bar
handleSearch(event) {
const searchTerm = event.target.value;
// Call the Apex method
searchRecords({ searchTerm: searchTerm })
.then((result) => {
this.lookupResults = result;
})
.catch((error) => {
console.error('Error fetching lookup results:', error);
});
}
// Fires when a user clicks a search result
handleSelection(event) {
const selectedId = event.target.dataset.id;
const selectedName = event.target.dataset.name;
const selectedItem = { Id: selectedId, Name: selectedName };
// Add the clicked item to our selected array
this.selectedValues = [...this.selectedValues, selectedItem];
// Clear the search results so the dropdown closes
this.lookupResults = [];
}
}
Notice how we used
this.selectedValues = [...this.selectedValues, selectedItem]; instead of .push()? In LWC, using the spread operator (...) creates a completely new array. This is required to force the HTML template to "react" and re-render the screen. If you just use push(), the screen might not update!
Step 5: Add the CSS Styling
Open multiSelectLookup.css. We need to style the dropdown so it floats below the input field, just like a standard Salesforce lookup.
.lookup-container {
position: relative;
display: block;
width: 100%;
}
/* Floating Dropdown styling */
.lookup-results {
list-style: none;
padding: 0;
margin: 0;
position: absolute;
width: 100%;
max-height: 200px;
overflow-y: auto;
border: 1px solid #ccc;
background-color: #fff;
z-index: 999;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.lookup-results li {
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.lookup-results li:hover {
background-color: #f3f3f3;
}
/* Selected Items styling */
.selected-values {
list-style: none;
padding: 10px;
margin-top: 10px;
border: 1px dashed #ccc;
background-color: #f9f9f9;
border-radius: 4px;
}
.selected-values li {
display: inline-block;
padding: 4px 8px;
margin: 4px;
background-color: #0070d2;
color: white;
border-radius: 16px;
font-size: 0.85rem;
}
To make this a truly reusable tool for your entire org, consider adding these enhancements later:
- Add an
@api objectApiNameproperty so you can pass in the target object from the parent component instead of hardcoding "Account" in Apex. - Add a "Remove" button (an X icon) next to the selected pills so users can un-select a record if they make a mistake.
- Use JavaScript debouncing on the
handleSearchmethod so it doesn't call Apex on every single keystroke.
Conclusion
By combining a lightning-input, a custom dropdown list, and an Apex search controller, you have successfully built a custom multi-select lookup component. This pattern is foundational for LWC development, teaching you how to manage user state, fetch server-side data, and manipulate CSS for modern UI design.
Happy coding!