Salesforce · · 16 min read

The Lightning Web Component Library

A guide to the Lightning Web Component Library — what it is, when to use library components vs building your own, how to extend base components like lightning-datatable, and a project building a data entry form with library components.

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:

  1. What is the LWC Library? — Where it lives, how to browse it, and what it includes.
  2. Using LWC library components vs building your own — The decision framework for choosing between base components and custom code.
  3. Extending an LWC library blueprint — How to extend lightning-datatable to add custom column types.
  4. Section Notes: The LWC Library cheat sheet — A reference of the most useful base components organized by category.
  5. 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 individual lightning-input-field components inside it and control the arrangement, but it still handles the save operation.
  • lightning-record-view-form — Read-only display of record fields using lightning-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-board or lightning-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 that lightning-datatable cannot 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-combobox does 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

ComponentPurpose
lightning-inputText, number, email, password, URL, tel, date, time, datetime, checkbox, toggle, file, color
lightning-textareaMulti-line text input
lightning-comboboxSingle-select dropdown
lightning-radio-groupRadio button group
lightning-checkbox-groupCheckbox group
lightning-sliderNumeric range slider
lightning-input-fieldField-aware input (use inside lightning-record-edit-form)
lightning-dual-listboxTwo-panel multi-select

Data Display

ComponentPurpose
lightning-datatableTabular data with sorting, selection, inline edit
lightning-treeHierarchical data display
lightning-cardContainer with header, body, and footer
lightning-tileCompact record summary
lightning-formatted-textRenders text with link and newline handling
lightning-formatted-numberLocale-aware number formatting
lightning-formatted-date-timeLocale-aware date/time formatting
lightning-formatted-urlClickable URL display
lightning-badgeSmall label/tag
lightning-output-fieldRead-only field display (use inside lightning-record-view-form)

Layout and Structure

ComponentPurpose
lightning-layoutFlexbox-based grid container
lightning-layout-itemColumn inside a layout
lightning-accordionCollapsible sections
lightning-accordion-sectionIndividual collapsible panel
lightning-tabsetTab container
lightning-tabIndividual tab panel
lightning-modalModal dialog (available as a base class to extend)

Actions and Navigation

ComponentPurpose
lightning-buttonStandard button
lightning-button-groupGrouped buttons
lightning-button-iconIcon-only button
lightning-button-menuDropdown menu button
lightning-menu-itemItem inside a button menu
lightning-breadcrumbNavigation breadcrumb item
lightning-vertical-navigationSide navigation menu

Feedback and Progress

ComponentPurpose
lightning-spinnerLoading spinner
lightning-progress-barHorizontal progress bar
lightning-progress-indicatorStep-based progress (path)
lightning-helptextTooltip icon with help text

Records (No Apex Required)

ComponentPurpose
lightning-record-formAuto-generated view/edit form
lightning-record-edit-formCustom-layout edit form with save handling
lightning-record-view-formCustom-layout read-only form

Utility

ComponentPurpose
lightning-iconSLDS icon display
lightning-avatarUser avatar image
lightning-file-uploadFile upload with drag-and-drop
lightning-pillRemovable tag (used in lookup fields)
lightning-pill-containerContainer 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 required attribute on each input handles required-field checking. The pattern attribute on the phone input handles format validation. The type="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 size and medium-device-size attributes on lightning-layout-item handle 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.