What Salesforce LWC components are, where to find the official base component list, and when to use base, custom or prebuilt components, with tested code examples.

Every Salesforce UI you ship in Lightning Experience is built from Lightning Web Components (LWC). When you need a new screen, there are three ways to get there: compose the base components Salesforce gives you, build your own custom component, or install a prebuilt component from a package. This guide explains each option with code you can deploy, shows where base components stop, and gives you a simple way to decide.
A Lightning web component is a reusable custom HTML element with its own API, built on web standards. A component is a folder (a "bundle") with an HTML template, a JavaScript class that extends LightningElement, an optional CSS file and a .js-meta.xml configuration file. The configuration file declares where the component can be used: Lightning App Builder pages, Experience Builder sites, Flow Builder, quick actions, the utility bar and more.
If you are starting from zero, the LWC Developer Guide and the LWC Recipes sample app are the best official starting points. This guide focuses on the question that comes next: which kind of component should you use for a given screen?
Base components are the prebuilt components Salesforce ships in the lightning namespace, written as lightning-component-name in your markup (lightning-button, lightning-datatable, lightning-record-edit-form…). They follow the Salesforce Lightning Design System (SLDS), and Salesforce recommends using them wherever possible instead of re-implementing an SLDS blueprint yourself, because you get design updates automatically.
Salesforce groups them into categories: Actions and Menus, Containers, Images, Input, Forms, Navigation, Notifications, Output, Progress, and Tables and Trees, plus utility modules such as lightning/navigation or lightning/uiRecordApi.
Heads-up if you have an old bookmark: the legacy Component Reference inside the Lightning Component Library is no longer updated, and Salesforce stops publishing it from Spring ’26. The current reference lives at developer.salesforce.com/docs/platform/lightning-component-reference. The in-org version (https://<your-domain>.lightning.force.com/docs/component-library) is still useful because it also lists components installed from managed packages.
Base components you will use most often:
| Need | Base component |
|---|---|
| Show records in a table | lightning-datatable |
| Hierarchical rows | lightning-tree-grid |
| View or edit a record with layout and validation | lightning-record-form, lightning-record-edit-form, lightning-record-view-form |
| Form inputs | lightning-input, lightning-combobox, lightning-dual-listbox, lightning-textarea |
| Layout and containers | lightning-card, lightning-layout, lightning-tabset, lightning-accordion |
| Modal dialogs | the lightning/modal module (extend LightningModal) |
| Show locations | lightning-map |
| Progress | lightning-progress-indicator, lightning-progress-bar |
Most real projects mix all three. The skill is knowing which one each screen deserves.
lightning-datatableA read-only table of technology accounts on a record, app or home page. Base components handle the rendering; you provide the data.
Apex controller (AccountController.cls):
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getTechAccounts() {
return [
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
WITH USER_MODE
ORDER BY Name
LIMIT 100
];
}
}
JavaScript (techAccounts.js):
import { LightningElement, wire } from 'lwc';
import getTechAccounts from '@salesforce/apex/AccountController.getTechAccounts';
const COLUMNS = [
{ label: 'Account Name', fieldName: 'Name' },
{ label: 'Industry', fieldName: 'Industry' },
{ label: 'Annual Revenue', fieldName: 'AnnualRevenue', type: 'currency' }
];
export default class TechAccounts extends LightningElement {
columns = COLUMNS;
@wire(getTechAccounts)
accounts;
}
Template (techAccounts.html):
<template>
<lightning-card title="Technology accounts" icon-name="standard:account">
<template lwc:if={accounts.data}>
<lightning-datatable
key-field="Id"
data={accounts.data}
columns={columns}
hide-checkbox-column>
</lightning-datatable>
</template>
<template lwc:elseif={accounts.error}>
<p class="slds-p-around_medium">Accounts could not be loaded.</p>
</template>
</lightning-card>
</template>
Configuration (techAccounts.js-meta.xml):
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Use your org's current API version. Notes: a wired property receives { data, error }; @AuraEnabled(cacheable=true) is required for an Apex method you call with @wire; WITH USER_MODE makes the query respect the running user's object and field permissions. You also need an Apex test class before you can deploy to production.
That is four files for a read-only table. Add search, filters, pagination, sorting on the server and inline edit with saving, and the component grows quickly. Each feature is more JavaScript, more Apex and more tests to maintain.

lightning-datatable on a Lightning app page (Sales Workspace).@api and eventsCustom components shine when the UI is specific to your business. The core patterns are composition (a parent uses child components), public properties with @api, and custom events to talk back to the parent.
Child (c-stage-chip): displays a stage and tells the parent when it is clicked.
// stageChip.js
import { LightningElement, api } from 'lwc';
export default class StageChip extends LightningElement {
@api label;
@api count = 0;
handleClick() {
this.dispatchEvent(new CustomEvent('select', { detail: { label: this.label } }));
}
}
<!-- stageChip.html -->
<template>
<lightning-button label={label} onclick={handleClick}></lightning-button>
<lightning-badge label={count} class="slds-m-left_x-small"></lightning-badge>
</template>
Parent: renders one chip per stage and reacts to the select event.
<!-- pipelineStages.html -->
<template>
<template for:each={stages} for:item="stage">
<c-stage-chip
key={stage.label}
label={stage.label}
count={stage.count}
onselect={handleSelect}>
</c-stage-chip>
</template>
<p class="slds-m-top_small">Selected: {selected}</p>
</template>
// pipelineStages.js
import { LightningElement } from 'lwc';
export default class PipelineStages extends LightningElement {
selected = 'None';
stages = [
{ label: 'Prospecting', count: 12 },
{ label: 'Negotiation', count: 4 },
{ label: 'Closed Won', count: 7 }
];
handleSelect(event) {
this.selected = event.detail.label;
}
}
Custom components are where your differentiating logic belongs: pricing rules, a guided process unique to your team, an integration-specific screen. The trade-off is ownership: every component you write is code to review, test and update with each of the three Salesforce releases per year.

Base components are building blocks, not finished business UIs. Common requests that have no single base component:
You can build all of these from base components plus custom code, and many teams do. It is also where most of the time goes, as we explain in what it costs to build and maintain these yourself.

Prebuilt components move most of that work into a package. Here is the account table again, using the Avonni Data Table (avonni-dd-datatable) from the Avonni LWC component library. In query mode, the component runs its own query: you describe the object, filter and columns, and it handles fetching, search, filters, pagination and the inline editing UI. No Apex controller to write. Saving the edited values to Salesforce is your call: you handle the component's save event with one short handler that uses updateRecord from lightning/uiRecordApi, so still no Apex.
<!-- techAccountsAvonni.html -->
<template>
<avonni-dd-datatable
lwc:ref="table"
header-title="Technology accounts"
query={query}
columns={columns}
search-fields={searchFields}
filters={filters}
show-pagination
items-per-page="25"
draft-values={draftValues}
onsave={handleSave}>
</avonni-dd-datatable>
</template>
// techAccountsAvonni.js
import { LightningElement } from 'lwc';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class TechAccountsAvonni extends LightningElement {
query = {
objectApiName: 'Account',
filter: "Industry = 'Technology'",
orderBy: 'Name ASC',
limit: 100
};
columns = [
{ fieldName: 'Name', label: 'Account Name', type: 'text', searchable: true, sortable: true },
{ fieldName: 'Industry', label: 'Industry', type: 'text', filterable: true },
{ fieldName: 'AnnualRevenue', label: 'Annual Revenue', type: 'currency', editable: true, sortable: true }
];
searchFields = ['Name'];
filters = ['Rating'];
draftValues = [];
async handleSave(event) {
// Each draft is { Id, ...changedFields, index }: drop the row index added by the table.
const records = event.detail.draftValues.map(({ index, ...fields }) => ({ fields }));
try {
await Promise.all(records.map((record) => updateRecord(record)));
this.draftValues = [];
this.refs.table.refresh(true);
this.dispatchEvent(new ShowToastEvent({ title: 'Saved', message: `${records.length} record(s) saved`, variant: 'success' }));
} catch (error) {
this.dispatchEvent(new ShowToastEvent({ title: 'Save failed', message: error.body?.message ?? error.message, variant: 'error' }));
}
}
}
Compare with Example 1: no Apex class, no Apex test class, no wire adapter. Search, filters, pagination and the inline editing UI come with the component; the only code you write is the save handler.

What to know:
avonni-* tags in your template, like base components. No JavaScript import is needed for the component itself.save event with the edited rows in event.detail.draftValues; your handler persists them (here with updateRecord, which also respects the user's permissions), clears draftValues and calls refresh() to reload the rows.
onsave handler writes it to Salesforce.items-per-page sets the page size. Set it below the query limit: with the default page size, the 100 rows of this query all fit on one page and pagination has nothing to do.items array instead of a query.
| Situation | Best fit | Why |
|---|---|---|
| A standard form, button, tabs or a simple read-only list | Base components | Free, SLDS-native, maintained by Salesforce |
| A screen that encodes logic unique to your business | Custom LWC | It is your differentiator, so own it |
| A common but heavy UI: rich data table, Kanban, scheduler, pivot, map, relationship graph | Prebuilt components | Solved problems; the upkeep moves to the vendor |
| You only need a small helper and you want full code ownership | Open source (copy into your repo) | Free, but you maintain the fork |
| Admins, not developers, will build the screen | Not LWC code at all: Lightning App Builder or Flow | See App Builder components or the Flow screen components guide |
A practical split many teams use: base and custom components for what is genuinely yours, prebuilt components for the heavy, well-solved surfaces.
Before you depend on any third-party component, check:
For reference, Avonni answers these as follows: LWC Components is a managed package on AppExchange that passed the security review; components compose inside your own markup and are wired through documented events and methods; there are 55+ components, 15 of them data-driven with a built-in query engine; you get 10 free production licenses and unlimited sandbox use; and an MCP server lets assistants like Claude Code, Cursor or Copilot look up the real attributes before writing markup. Explore the 55+ LWC components.
LWC is the modern, web-standards-based programming model; Aura is the original Lightning framework. Salesforce recommends LWC for new development. The two can coexist on the same page, and an Aura component can contain an LWC, but not the reverse.
In the Lightning Component Reference on developer.salesforce.com. The legacy Component Reference in the Component Library is no longer published from Spring ’26.
Not always. Wire adapters from lightning/uiRecordApi and the GraphQL wire adapter can read data without Apex. For list queries with custom filters, many teams use Apex, or a prebuilt component that runs its own query.
Only within the limits of the platform: the lwc:external directive for native custom elements is not currently supported on the Salesforce Platform, and npm packages must be bundled and uploaded as static resources rather than imported directly. Managed-package components, like Avonni's, are used as regular namespaced tags.
It depends on the source. Base components are included with Salesforce. Open-source repositories are free but you maintain the code. Commercial packages are licensed. Avonni LWC Components, for example, includes 10 free production licenses.
Start free with 10 production licenses. All 3 Avonni packages included.