Part 81: The Lightning Web Component Library
We are ten posts deep into the LWC topic now, and you have built a strong foundation. You know how HTML templates, JavaScript controllers, and CSS work together inside a component. You understand the DOM, reactivity, lifecycle hooks, and how data moves between parent and child components. You have even wired up Apex to pull live data from the server. All of that was about understanding the mechanics of the framework itself.
This post is about what Salesforce gives you for free.
The Lightning Web Component Library is Salesforce’s collection of prebuilt, production-ready base components that you can drop into your own LWC markup. These are not third-party packages or community plugins. They are first-party components maintained by Salesforce, tested against every release, and designed to look and behave exactly like the rest of the Lightning Experience UI. Knowing when to use them, when to build your own, and how to extend them when they almost-but-not-quite fit your needs is one of the most important skills you can develop as an LWC developer.
Here is what we will cover:
- What is the LWC Library? — Where it lives, how to browse it, and what it includes.
- Using LWC library components vs building your own — The decision framework for choosing between base components and custom code.
- Extending an LWC library blueprint — How to extend
lightning-datatableto add custom column types. - Section Notes: The LWC Library cheat sheet — A reference of the most useful base components organized by category.
- PROJECT: Create a data entry form component using only LWC library components — A complete, functional form built entirely from base components.
Let’s get into it.
What is the LWC Library?
The LWC Library is the official set of base Lightning components that ship with the Salesforce platform. Every Salesforce org has access to them out of the box. You do not install anything. You do not import a package. You just use them in your HTML templates by referencing their tag names.
Where to Find It
The canonical reference lives at the Salesforce Component Library. This is a searchable, browsable site that documents every base component. For each one you get a description, a list of attributes, the events it fires, and working examples in the playground. Bookmark it. You will visit it constantly.
Every base component follows the lightning- namespace convention. When you see a tag like <lightning-button>, <lightning-input>, or <lightning-datatable>, that is a base component from the library.
What the Library Includes
The library covers a wide range of UI needs:
- Form inputs — Text fields, checkboxes, radio groups, comboboxes, date pickers, sliders, and more.
- Data display — Datatables, trees, cards, tiles, formatted text, formatted numbers, and formatted dates.
- Navigation — Tabs, vertical navigation, breadcrumbs, and menu items.
- Feedback — Spinners, progress bars, progress indicators, and toast messages through the platform event.
- Layout — Layout, layout-item, accordion, and card components for structuring your page.
- Actions — Buttons, button groups, button menus, and button icons.
- File handling — File upload and file card components.
- Records — Lightning record form, record edit form, and record view form for interacting with Salesforce records without writing Apex.
Each of these is a fully encapsulated LWC with its own shadow DOM, styles, and behavior. They handle accessibility, keyboard navigation, error states, and responsive behavior for you.
The Three Record Form Components
Worth calling out specifically are the three record form components, because they solve one of the most common needs in Salesforce development:
lightning-record-form— An all-in-one component that handles both viewing and editing a record. You give it an object API name and a record ID, and it renders the fields, handles validation, and saves to the database. No Apex required.lightning-record-edit-form— Gives you more control over the edit experience. You lay out individuallightning-input-fieldcomponents inside it and control the arrangement, but it still handles the save operation.lightning-record-view-form— Read-only display of record fields usinglightning-output-field. Again, no Apex needed.
If your requirement is “display or edit a Salesforce record,” start with these. They eliminate entire categories of code you would otherwise have to write and maintain.
Using LWC Library Components vs Building Your Own
This is the most common judgment call you will make as an LWC developer. The answer is not always obvious, so here is a framework for thinking about it.
Default to Base Components
The base components are well-tested, accessible, and visually consistent with the rest of Lightning Experience. Every time you build something from scratch instead of using a base component, you take on the responsibility of handling focus management, keyboard navigation, ARIA attributes, error states, mobile responsiveness, and visual consistency with the platform. That is a lot of work for a text input.
The rule of thumb: use a base component unless you have a specific reason not to.
When to Build Custom
There are legitimate reasons to go custom:
- The base component does not exist for your use case. There is no
lightning-kanban-boardorlightning-calendar-view. If your requirement is a UI pattern the library does not cover, you build it. - You need a different visual treatment. Maybe you need a card that looks nothing like
lightning-card, or a table layout thatlightning-datatablecannot produce. Styling overrides on base components are intentionally limited because of shadow DOM encapsulation. - Performance at scale. If you are rendering thousands of items and the base component adds too much overhead per item, a leaner custom implementation might be necessary.
- You need behavior the base component does not support. For instance,
lightning-comboboxdoes not support multi-select out of the box. You can either find a workaround, use a different base component, or build your own multi-select.
The Hybrid Approach
Often the best answer is a mix. You build a custom parent component that handles your unique layout and business logic, but inside it you use base components for the individual controls. A custom search panel, for example, might use lightning-input for the search box, lightning-combobox for filter dropdowns, lightning-button for the search action, and lightning-datatable for results. You get your custom UX without reinventing every widget.
A Quick Comparison
Consideration | Base Component | Custom Component
---------------------------|--------------------------|---------------------------
Development speed | Fast | Slower
Accessibility | Built-in | You handle it
Visual consistency | Automatic | Manual effort
Upgrade safety | Salesforce maintains | You maintain
Customization depth | Limited by component API | Unlimited
Performance (at scale) | Good for most cases | Can be optimized further
When I work on a real project, I estimate that 70-80% of the individual UI elements in my components come from the base library. The remaining 20-30% is custom markup for layout, specialized interactions, or visual designs that do not map to an existing base component.
Extending an LWC Library Blueprint (Extending Data Tables)
Sometimes a base component does 90% of what you need but is missing one key feature. The lightning-datatable component is a perfect example, and Salesforce specifically designed it to be extendable.
Why Extend Instead of Rebuild?
lightning-datatable gives you sorting, column resizing, row selection, infinite scrolling, inline editing, and more. Rebuilding all of that from scratch to add one custom column type would be a massive waste of effort. Instead, you extend the base component and add just the piece that is missing.
How Extension Works
You create a new LWC that extends LightningDatatable (the JavaScript class behind the base component). In your subclass, you define custom column types that reference their own HTML templates for rendering.
Here is a concrete example. Say you want a datatable column that shows a colored status badge instead of plain text.
First, create a template for the custom cell. This goes in a file called statusBadge.html inside your component folder:
<!-- statusBadge.html -->
<template>
<span class={badgeClass}>
{value}
</span>
</template>
Next, create the extended datatable component. The JavaScript file imports LightningDatatable and registers the custom type:
// customDatatable.js
import LightningDatatable from 'lightning/datatable';
import statusBadge from './statusBadge.html';
export default class CustomDatatable extends LightningDatatable {
static customTypes = {
statusBadge: {
template: statusBadge,
standardCellLayout: true,
typeAttributes: ['statusColor']
}
};
}
Now in a parent component, you use your custom datatable instead of the standard one:
<!-- parentComponent.html -->
<template>
<c-custom-datatable
key-field="id"
data={tableData}
columns={columns}>
</c-custom-datatable>
</template>
And in the parent’s JavaScript, you define the columns including your custom type:
// parentComponent.js
import { LightningElement } from 'lwc';
export default class ParentComponent extends LightningElement {
columns = [
{ label: 'Name', fieldName: 'name', type: 'text' },
{ label: 'Amount', fieldName: 'amount', type: 'currency' },
{
label: 'Status',
fieldName: 'status',
type: 'statusBadge',
typeAttributes: {
statusColor: { fieldName: 'statusColor' }
}
}
];
tableData = [
{ id: '1', name: 'Acme Corp', amount: 50000, status: 'Closed Won', statusColor: 'green' },
{ id: '2', name: 'Global Inc', amount: 75000, status: 'Negotiation', statusColor: 'orange' },
{ id: '3', name: 'Beta LLC', amount: 30000, status: 'Closed Lost', statusColor: 'red' }
];
}
You get all the standard datatable features — sorting, resizing, row selection — plus your custom status badge column. This is the power of extending rather than rebuilding. You write maybe 30 lines of code instead of thousands.
What Else Can You Extend?
The lightning-datatable extension pattern is the most commonly used one. Other base components are not designed for class-based extension in the same way. However, you can compose any base component inside your own. Wrapping a lightning-input inside a custom component that adds validation logic, character counting, or conditional formatting is a form of extension through composition. This pattern works for every base component.
Section Notes: The LWC Library Cheat Sheet
Here is a quick reference of the base components you will use most often, organized by category. Keep this handy.
Form Inputs
| Component | Purpose |
|---|---|
lightning-input | Text, number, email, password, URL, tel, date, time, datetime, checkbox, toggle, file, color |
lightning-textarea | Multi-line text input |
lightning-combobox | Single-select dropdown |
lightning-radio-group | Radio button group |
lightning-checkbox-group | Checkbox group |
lightning-slider | Numeric range slider |
lightning-input-field | Field-aware input (use inside lightning-record-edit-form) |
lightning-dual-listbox | Two-panel multi-select |
Data Display
| Component | Purpose |
|---|---|
lightning-datatable | Tabular data with sorting, selection, inline edit |
lightning-tree | Hierarchical data display |
lightning-card | Container with header, body, and footer |
lightning-tile | Compact record summary |
lightning-formatted-text | Renders text with link and newline handling |
lightning-formatted-number | Locale-aware number formatting |
lightning-formatted-date-time | Locale-aware date/time formatting |
lightning-formatted-url | Clickable URL display |
lightning-badge | Small label/tag |
lightning-output-field | Read-only field display (use inside lightning-record-view-form) |
Layout and Structure
| Component | Purpose |
|---|---|
lightning-layout | Flexbox-based grid container |
lightning-layout-item | Column inside a layout |
lightning-accordion | Collapsible sections |
lightning-accordion-section | Individual collapsible panel |
lightning-tabset | Tab container |
lightning-tab | Individual tab panel |
lightning-modal | Modal dialog (available as a base class to extend) |
Actions and Navigation
| Component | Purpose |
|---|---|
lightning-button | Standard button |
lightning-button-group | Grouped buttons |
lightning-button-icon | Icon-only button |
lightning-button-menu | Dropdown menu button |
lightning-menu-item | Item inside a button menu |
lightning-breadcrumb | Navigation breadcrumb item |
lightning-vertical-navigation | Side navigation menu |
Feedback and Progress
| Component | Purpose |
|---|---|
lightning-spinner | Loading spinner |
lightning-progress-bar | Horizontal progress bar |
lightning-progress-indicator | Step-based progress (path) |
lightning-helptext | Tooltip icon with help text |
Records (No Apex Required)
| Component | Purpose |
|---|---|
lightning-record-form | Auto-generated view/edit form |
lightning-record-edit-form | Custom-layout edit form with save handling |
lightning-record-view-form | Custom-layout read-only form |
Utility
| Component | Purpose |
|---|---|
lightning-icon | SLDS icon display |
lightning-avatar | User avatar image |
lightning-file-upload | File upload with drag-and-drop |
lightning-pill | Removable tag (used in lookup fields) |
lightning-pill-container | Container for multiple pills |
This is not exhaustive. The full library has over 70 components. But these are the ones you will reach for on a daily basis.
PROJECT: Create a Data Entry Form Component Using Only LWC Library Components
Time to put it all together. We are going to build a complete contact data entry form using nothing but base library components. No custom HTML inputs, no custom styling hacks. Just the library.
Requirements
The form should:
- Collect first name, last name, email, phone, department (from a dropdown), preferred contact method (radio buttons), and additional notes.
- Validate required fields before submission.
- Display a confirmation message on successful submission.
- Be laid out in a two-column grid on desktop and stack to a single column on mobile.
The Component Files
contactEntryForm.html
<template>
<lightning-card title="New Contact" icon-name="standard:contact">
<div class="slds-p-around_medium">
<template if:false={isSubmitted}>
<!-- Two-column layout -->
<lightning-layout multiple-rows>
<!-- First Name -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-input
label="First Name"
value={firstName}
onchange={handleFirstNameChange}
required>
</lightning-input>
</lightning-layout-item>
<!-- Last Name -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-input
label="Last Name"
value={lastName}
onchange={handleLastNameChange}
required>
</lightning-input>
</lightning-layout-item>
<!-- Email -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-input
type="email"
label="Email Address"
value={email}
onchange={handleEmailChange}
required>
</lightning-input>
</lightning-layout-item>
<!-- Phone -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-input
type="tel"
label="Phone Number"
value={phone}
onchange={handlePhoneChange}
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
message-when-pattern-mismatch="Expected format: 123-456-7890">
</lightning-input>
</lightning-layout-item>
<!-- Department -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-combobox
label="Department"
value={department}
options={departmentOptions}
onchange={handleDepartmentChange}
placeholder="Select a department"
required>
</lightning-combobox>
</lightning-layout-item>
<!-- Preferred Contact Method -->
<lightning-layout-item size="12" medium-device-size="6" padding="around-small">
<lightning-radio-group
label="Preferred Contact Method"
options={contactMethodOptions}
value={preferredContactMethod}
onchange={handleContactMethodChange}
type="button"
required>
</lightning-radio-group>
</lightning-layout-item>
<!-- Notes -->
<lightning-layout-item size="12" padding="around-small">
<lightning-textarea
label="Additional Notes"
value={notes}
onchange={handleNotesChange}
max-length="500"
placeholder="Any additional information about this contact...">
</lightning-textarea>
</lightning-layout-item>
</lightning-layout>
<!-- Submit Button -->
<div class="slds-p-around_small slds-text-align_center">
<lightning-button
variant="brand"
label="Submit Contact"
onclick={handleSubmit}
icon-name="utility:save">
</lightning-button>
</div>
</template>
<!-- Confirmation -->
<template if:true={isSubmitted}>
<div class="slds-text-align_center slds-p-around_large">
<lightning-icon
icon-name="action:approval"
size="large"
alternative-text="Success">
</lightning-icon>
<h2 class="slds-text-heading_medium slds-m-top_medium">
Contact Submitted Successfully
</h2>
<p class="slds-m-top_small slds-text-color_weak">
{firstName} {lastName} has been added.
</p>
<lightning-button
variant="neutral"
label="Add Another Contact"
onclick={handleReset}
class="slds-m-top_medium">
</lightning-button>
</div>
</template>
</div>
</lightning-card>
</template>
contactEntryForm.js
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class ContactEntryForm extends LightningElement {
firstName = '';
lastName = '';
email = '';
phone = '';
department = '';
preferredContactMethod = '';
notes = '';
isSubmitted = false;
get departmentOptions() {
return [
{ label: 'Sales', value: 'Sales' },
{ label: 'Engineering', value: 'Engineering' },
{ label: 'Marketing', value: 'Marketing' },
{ label: 'Support', value: 'Support' },
{ label: 'Human Resources', value: 'Human Resources' },
{ label: 'Finance', value: 'Finance' }
];
}
get contactMethodOptions() {
return [
{ label: 'Email', value: 'Email' },
{ label: 'Phone', value: 'Phone' },
{ label: 'Text', value: 'Text' }
];
}
handleFirstNameChange(event) {
this.firstName = event.detail.value;
}
handleLastNameChange(event) {
this.lastName = event.detail.value;
}
handleEmailChange(event) {
this.email = event.detail.value;
}
handlePhoneChange(event) {
this.phone = event.detail.value;
}
handleDepartmentChange(event) {
this.department = event.detail.value;
}
handleContactMethodChange(event) {
this.preferredContactMethod = event.detail.value;
}
handleNotesChange(event) {
this.notes = event.detail.value;
}
handleSubmit() {
const allValid = [
...this.template.querySelectorAll('lightning-input'),
...this.template.querySelectorAll('lightning-combobox'),
...this.template.querySelectorAll('lightning-radio-group')
].reduce((validSoFar, inputField) => {
inputField.reportValidity();
return validSoFar && inputField.checkValidity();
}, true);
if (allValid) {
// In a real application, you would call an Apex method here
// to insert the Contact record. For this project, we simulate success.
const contactData = {
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
phone: this.phone,
department: this.department,
preferredContactMethod: this.preferredContactMethod,
notes: this.notes
};
console.log('Contact submitted:', JSON.stringify(contactData));
this.isSubmitted = true;
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: `Contact ${this.firstName} ${this.lastName} created.`,
variant: 'success'
})
);
} else {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: 'Please fill in all required fields correctly.',
variant: 'error'
})
);
}
}
handleReset() {
this.firstName = '';
this.lastName = '';
this.email = '';
this.phone = '';
this.department = '';
this.preferredContactMethod = '';
this.notes = '';
this.isSubmitted = false;
}
}
contactEntryForm.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
What to Notice
Count the base library components used in this form: lightning-card, lightning-layout, lightning-layout-item, lightning-input (four times with different types), lightning-combobox, lightning-radio-group, lightning-textarea, lightning-button (twice), and lightning-icon. That is ten distinct base components working together.
Now look at what you did not have to build:
- Validation — The
requiredattribute on each input handles required-field checking. Thepatternattribute on the phone input handles format validation. Thetype="email"on the email input handles email format validation. All built in. - Error messages — Each base component renders its own error messages below the field when validation fails. You did not write a single error message string for standard validations.
- Responsive layout — The
sizeandmedium-device-sizeattributes onlightning-layout-itemhandle the responsive behavior. Two columns on desktop, one on mobile. No media queries. - Accessibility — Every base component has proper ARIA attributes, label associations, and keyboard handling. Your form is accessible out of the box.
- Visual consistency — The form looks like every other form in Lightning Experience. No CSS overrides needed.
The validation approach in handleSubmit is worth studying. We gather all the validatable components using querySelectorAll, call reportValidity() on each one to trigger visual error states, and use reduce to check if everything passes. This is the standard pattern for form validation in LWC when you are not using lightning-record-edit-form.
Where to Go From Here
This project uses simulated submission. In a real application, you would wire the handleSubmit method to an @wire adapter or an imperative Apex call to insert the Contact record. We covered calling Apex from LWC in the earlier posts in this topic, so connecting this form to the backend is a natural next step.
You could also swap this entire approach for a lightning-record-edit-form if your fields map directly to a Salesforce object. That version would be even shorter because the form handles the save operation for you. The tradeoff is less control over layout and behavior.
That wraps up our look at the Lightning Web Component Library. The key takeaway is this: the library is not a convenience, it is a foundation. Every component you pull from it is a component you do not have to build, test, maintain, or make accessible. Learn what is available, default to base components, extend when you need to, and only build from scratch when the library genuinely cannot support your requirement. In the next post, we will continue building on these foundations as we move deeper into advanced LWC patterns.