Array.prototype.filter()), the table recalculates and renders matching rows instantly without requiring repeated roundtrips to the Salesforce database.
Providing instant record lookups inside custom dashboards, modal pickers, and console workspaces significantly improves user productivity. While server-side SOQL filtering is necessary for massive datasets, client-side filtering offers instantaneous, zero-latency feedback for datasets loaded into component memory. Below is a complete guide to building an accessible, responsive search table using standard Salesforce Lightning Design System (SLDS) styling.
1. How Client-Side Reactive Search Works in LWC
The client-side search pattern relies on three coordinated pieces inside the component lifecycle:
- Master Dataset (
data): Holds the immutable, original collection of records retrieved from an Apex wire adapter or mock store. - Search Query State (
searchTerm): Tracks the live input string captured from theonchangeevent on the search box. - Computed Getter (
filteredData): Automatically runs wheneversearchTermchanges, performing case-insensitive string matching across multiple object fields and returning only matching records.
- Reactivity Model: Evaluated via a JavaScript getter (
get filteredData()). - Search Scope: Multi-field matching across Name, Email, and custom identifiers using
String.toLowerCase().includes(). - UI Framework: Native SLDS Data Table utilities (
slds-table,slds-table_bordered). - Performance Threshold: Best suited for datasets under 1,000 records in memory.
2. Step-by-Step Implementation
searchTable.html)Combine a search input with an SLDS-styled data table, including empty-state messaging.
<template>
<lightning-card title="Directory Search" icon-name="standard:contact">
<div class="slds-p-around_medium">
<!-- Search Bar Input -->
<div class="slds-m-bottom_medium">
<lightning-input
type="search"
label="Search Directory"
placeholder="Search by name or email..."
value={searchTerm}
onchange={handleSearch}>
</lightning-input>
</div>
<!-- Responsive Table Container -->
<div class="slds-scrollable_x">
<table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped">
<thead>
<tr class="slds-line-height_reset">
<th scope="col">
<div class="slds-truncate" title="Name">Name</div>
</th>
<th scope="col">
<div class="slds-truncate" title="Email">Email</div>
</th>
<th scope="col">
<div class="slds-truncate" title="Role">Role</div>
</th>
</tr>
</thead>
<tbody>
<template for:each={filteredData} for:item="record">
<tr key={record.Id}>
<td data-label="Name">
<div class="slds-truncate font-bold">{record.Name}</div>
</td>
<td data-label="Email">
<div class="slds-truncate">{record.Email}</div>
</td>
<td data-label="Role">
<div class="slds-truncate">{record.Role}</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- Empty State Feedback -->
<template lwc:if={isResultEmpty}>
<div class="slds-text-align_center slds-p-around_large slds-text-color_weak">
No records found matching "{searchTerm}"
</div>
</template>
</div>
</lightning-card>
</template>
searchTable.js)Implement the reactive filtering logic and normalize search queries for case-insensitive checks.
import { LightningElement, track } from 'lwc';
export default class SearchTable extends LightningElement {
searchTerm = '';
@track masterData = [
{ Id: 'REC-001', Name: 'John Doe', Email: 'john.doe@example.com', Role: 'Technical Architect' },
{ Id: 'REC-002', Name: 'Jane Smith', Email: 'jane.smith@example.com', Role: 'System Administrator' },
{ Id: 'REC-003', Name: 'Alex Johnson', Email: 'alex.johnson@example.com', Role: 'Platform Developer' },
{ Id: 'REC-004', Name: 'Sara Connor', Email: 'sara.connor@example.com', Role: 'Release Engineer' }
];
handleSearch(event) {
this.searchTerm = event.target.value;
}
get filteredData() {
if (!this.searchTerm || this.searchTerm.trim() === '') {
return this.masterData;
}
const query = this.searchTerm.trim().toLowerCase();
return this.masterData.filter(record => {
const nameMatch = record.Name ? record.Name.toLowerCase().includes(query) : false;
const emailMatch = record.Email ? record.Email.toLowerCase().includes(query) : false;
const roleMatch = record.Role ? record.Role.toLowerCase().includes(query) : false;
return nameMatch || emailMatch || roleMatch;
});
}
get isResultEmpty() {
return this.filteredData.length === 0;
}
}
searchTable.css)
.font-bold {
font-weight: 600;
}
/* Ensure horizontal scrollability on narrow mobile viewports */
.slds-scrollable_x {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
3. Common Traps & Performance Best Practices
Writing statements like
this.data = this.data.filter(...) permanently overwrites your original dataset. Once a user backspaces or clears their search query, the original records are lost forever. Always maintain an untouched master collection and return filtered rows through a separate computed getter or secondary display array.
- Defensive Null Checks: Always verify that properties exist before calling
.toLowerCase()to prevent fatalCannot read properties of undefinedruntime exceptions. - Use Native SLDS Utility Classes: Replace manual CSS margin and padding declarations with standard classes like
slds-p-around_mediumandslds-m-bottom_mediumfor consistent theme alignment. - Client vs. Server Filtering Strategy: Use client-side getters for lists under 1,000 records. For larger enterprise tables, debounce input keystrokes and execute parameterized SOQL or SOSL queries on the server.
Summary
Building a search table with reactive client-side filtering delivers a fast, responsive user experience in Salesforce Lightning Web Components. By separating raw data from computed getters, enforcing case-insensitive checks, and styling tables with standard SLDS classes, developers can create polished directory views and interactive search utilities that scale cleanly.