Skip to main content

How to Build a Multi-Select Component in Salesforce LWC: Dual Listbox Guide

In plain words: A Multi-Select Component in Lightning Web Components (LWC) allows users to choose multiple items from an available pool and move them into a selected list. In Salesforce, this is built using the standard <lightning-dual-listbox> base component, which provides accessible transfer buttons, reordering controls, and automatic state tracking with minimal custom JavaScript.

Allowing users to select multiple options—such as assigning skill sets, selecting product categories, or allocating team roles—is a fundamental requirement in enterprise applications. Instead of building custom checkbox groups or messy multi-select picklists from scratch, Salesforce provides the native <lightning-dual-listbox> component. Below is a complete step-by-step guide to building and deploying a reusable multi-select component.

1. Understanding the Dual Listbox Architecture

The <lightning-dual-listbox> component provides side-by-side list selection out of the box with standard SLDS styling:

  • Options Array (options): A list of JavaScript objects representing the source items. Each item must contain a label (the display text) and a value (the unique identifier).
  • Selected Values Array (value): An array of strings containing the value keys of the items currently placed in the right-hand "Selected" box.
  • Change Event Dispatch (onchange): When items move between columns, the component fires a change event where event.detail.value contains the updated array of selected keys.
360 Dual Listbox Blueprint Card:
  • Base Component: <lightning-dual-listbox>
  • Required Attributes: label, source-label, selected-label, options, and value.
  • Event Payload: event.detail.value (returns an array of selected string values).
  • Validation Support: Supports required, min, and max item selection limits natively.

2. Step-by-Step Implementation

Step 1: Build the Template Markup (multiSelectDemo.html)
Use the base dual listbox component wrapped inside a card layout.
<template>
    <lightning-card title="Skills & Expertise Assignment" icon-name="standard:skill">
        <div class="slds-p-around_medium">
            <lightning-dual-listbox
                name="skills"
                label="Assign Technical Skills"
                source-label="Available Skills"
                selected-label="Selected Skills"
                options={skillOptions}
                value={selectedSkills}
                size="4"
                required
                onchange={handleSkillChange}>
            </lightning-dual-listbox>

            <!-- Real-time Selection Feedback -->
            <template lwc:if={hasSelectedSkills}>
                <div class="slds-box slds-theme_shade slds-m-top_medium">
                    <p class="slds-text-title_bold">Current Selection Payload:</p>
                    <p class="slds-text-color_success">{selectedSkillsSummary}</p>
                </div>
            </template>
        </div>
    </lightning-card>
</template>
Step 2: Implement the JavaScript Controller (multiSelectDemo.js)
Define the options array and handle change events cleanly.
import { LightningElement, track } from 'lwc';

export default class MultiSelectDemo extends LightningElement {
    // Array of available options
    skillOptions = [
        { label: 'Apex & Triggers', value: 'apex' },
        { label: 'Lightning Web Components (LWC)', value: 'lwc' },
        { label: 'Salesforce Flow Automation', value: 'flows' },
        { label: 'Integration & REST APIs', value: 'integration' },
        { label: 'Data Architecture & SOQL', value: 'data_arch' },
        { label: 'DevOps & CI/CD Pipelines', value: 'devops' }
    ];

    // Default pre-selected values
    @track selectedSkills = ['lwc', 'apex'];

    get hasSelectedSkills() {
        return this.selectedSkills && this.selectedSkills.length > 0;
    }

    get selectedSkillsSummary() {
        return this.selectedSkills.join(', ');
    }

    handleSkillChange(event) {
        // event.detail.value returns an array of selected value keys
        this.selectedSkills = event.detail.value;
        console.log('Updated selected values:', this.selectedSkills);
    }
}
Step 3: Configure Metadata XML (multiSelectDemo.js-meta.xml)
Expose the component to Lightning App Builder.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>60.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
    </targets>
</LightningComponentBundle>

3. Common Traps & Development Best Practices

Reactivity Trap: Passing Full Objects Instead of String Keys in value
The value attribute of <lightning-dual-listbox> expects an array of primitive strings (e.g., ['opt1', 'opt2']), NOT full objects (e.g., [{label: 'Option 1', value: 'opt1'}]). Passing objects directly will result in empty selections on the screen.
Core Rule: Populate options with { label, value } pairs, bind value strictly to a string array of selected keys, and extract event.detail.value inside your change handler.
  • Enforce Minimum / Maximum Limits: Use the built-in min and max attributes (e.g., min="1" max="3") to restrict how many options users can select without writing custom validation code.
  • Dynamic Schema Options: When loading options dynamically from an sObject Picklist field, use the getPicklistValues wire adapter from lightning/uiObjectInfoApi to keep options in sync with Salesforce metadata.
  • Set Custom Height: Use the size attribute (e.g., size="6") to control how many visible rows the listboxes display before introducing a scrollbar.

Summary

The <lightning-dual-listbox> component provides an accessible, robust solution for handling multi-selection in Lightning Web Components. By wiring reactive JavaScript arrays to standard options and value attributes, developers can implement multi-select interfaces with full validation support in minutes.