---
name: lwc-development
description: Build production-grade Salesforce Lightning Web Components (LWC) — component design, @wire vs imperative Apex, Lightning Data Service (LDS) and UI API, Lightning Message Service, events and composition, SLDS 2 styling and Lightning Base Components, Experience Cloud (LWR) sites, LWC in Flow screens, governor-limit-aware UI patterns, and security (CRUD/FLS, Locker/Lightning Web Security). Use this whenever the user mentions LWC, Lightning Web Components, Aura migration, Lightning App Builder, Experience Cloud components, Salesforce frontend, @wire, lightning-record-form, lightning-datatable, Lightning Message Channel, or asks how to build UI on the Salesforce platform. Also apply it when reviewing LWC code.
metadata:
  technology: Salesforce
  type: development
---

# Salesforce LWC Development

LWC is standard web components plus a platform that punishes sloppy data access with governor limits, sharing violations, and slow pages. The best LWC engineers get data through the platform's cached services first and write Apex last.

## 1. Data access decision — in this order

| Need | Use | Why |
|---|---|---|
| Show/edit one record's fields | `lightning-record-form` / `lightning-record-view-form` / `lightning-record-edit-form` | Zero Apex, FLS enforced, LDS cache, layout-aware |
| Read fields of the current record | `@wire(getRecord, { recordId, fields })` + `getFieldValue` | Cached via LDS; reactive to updates |
| Create/update/delete one record | `createRecord` / `updateRecord` / `deleteRecord` from `lightning/uiRecordApi` | Cache-aware, no Apex, no DML limits on your side |
| Picklist values, object info | `getPicklistValues`, `getObjectInfo` (`lightning/uiObjectInfoApi`) | Cached, respects record types |
| Related lists | `lightning/uiRelatedListApi` (`getRelatedListRecords`) | Cached, respects layout |
| Lists with filtering/aggregation/joins | **Apex `@AuraEnabled(cacheable=true)`** with `@wire` | Only when UI API can't express the query |
| Mutations with business logic, multiple objects, callouts | **Apex imperative call** (`await method({...})`) | Needs `WITH USER_MODE`/security check |
| Complex search (SOSL) | Apex cacheable | — |
| GraphQL flexibility (multiple objects, pagination) | `lightning/uiGraphQLApi` (`gql`) | Cached, declarative |

Reach for Apex only after confirming the UI API can't do it. Every Apex method is code to test, secure, and maintain.

## 2. Component design

- **One responsibility per component.** Composite screens are containers (own data, handle events) with child presentational components (receive `@api` props, emit events).
- **Public API**: `@api` properties with JSDoc; getters for derived values (never store computed state).
- **Communication**: parent → child via `@api` and public methods; child → parent via `CustomEvent` (`this.dispatchEvent(new CustomEvent('select', { detail: { id } }))`); unrelated components via **Lightning Message Service** (`lightning/messageService` + a `MessageChannel` metadata file). Avoid `pubsub` libraries.
- **Events**: lowercase, no `on` prefix, no hyphens in the name; `bubbles: false, composed: false` by default; escalate to `bubbles: true` only when crossing one level.
- **Slots** for layout composition (`<slot name="actions">`).
- **Reactivity**: fields are reactive; arrays/objects need reassignment (`this.items = [...this.items, x]`), not mutation. `@track` only for deep mutation of objects you can't reassign — rare.
- **Lifecycle**: `connectedCallback` for setup that needs the DOM parent (subscriptions), `renderedCallback` guarded with a flag (it runs on every render), `disconnectedCallback` to unsubscribe LMS and remove listeners. Never fetch in `renderedCallback`.
- **Async**: `async/await` with `try/catch`; errors surfaced via `lightning/platformShowToastEvent` or an inline `<template lwc:if={error}>` message. Use `reduceErrors`-style helpers to flatten `error.body.message` / `pageErrors` / `fieldErrors`.
- **Templates**: `lwc:if` / `lwc:elseif` / `lwc:else` (not `if:true`), `for:each` with `key`, `lwc:ref` for element access, `lwc:spread` for prop forwarding.

## 3. Wire vs imperative — the rule

- `@wire` for **read data that should stay in sync**. It's declarative, cached, and refreshes on `refreshApex` or when LDS knows the record changed.
- Imperative for **mutations** and **reads that depend on user action** (search on click). After an imperative mutation via Apex, call `refreshApex(this.wiredResult)` or `notifyRecordUpdateAvailable` (`lightning/uiRecordApi`) so LDS-backed components update.
- Wired Apex must be `cacheable=true`; cached methods cannot do DML.
- Wire parameters are reactive with `$recordId` syntax; guard against `undefined` on first render.

## 4. Apex written *for* LWC

Every `@AuraEnabled` method the frontend calls:

- `with sharing` on the class (or `inherited sharing` for utilities); use `WITH USER_MODE` in SOQL/SOSL and `Database.insert(records, AccessLevel.USER_MODE)` for DML so CRUD/FLS is enforced by the platform.
- Bulk-safe signature — accept a `List<Id>`/wrapper, not one Id per call, so the UI can batch.
- Return **DTO wrapper classes** (`public class OrderRow { @AuraEnabled public Id id; ... }`) instead of raw SObjects when the UI needs shaped data. Keeps the contract stable and hides fields the UI shouldn't see.
- Throw `AuraHandledException` with user-safe messages; log the real error to a custom logging object/Platform Event.
- Server-side pagination (`LIMIT`/`OFFSET` or keyset) for lists; never return thousands of rows to the browser.
- Cacheable reads separated from mutations in different methods (or classes).
- Test class covers positive, negative (no access), and bulk paths — see `salesforce-frontend-testing`.

## 5. Styling and UX

- **Lightning Base Components first** (`lightning-button`, `lightning-input`, `lightning-datatable`, `lightning-card`, `lightning-combobox`). They are accessible, themed, and mobile-tested.
- **SLDS 2 styling hooks** (`--slds-g-*` global tokens, `--slds-c-*` component hooks) to customise; never override base component internals or use `/deep/`.
- Own CSS is scoped per component; use the `:host` selector for the root. For shared styles, a CSS-only module component imported with `import sharedStyles from 'c/sharedStyles'` and `static stylesheets = [sharedStyles]`.
- Layout with `lightning-layout` / SLDS grid classes; responsive by default; test in App Builder small/large device previews.
- Loading with `lightning-spinner` inside a relative container; empty states designed, not blank; errors inline near the action.
- Icons via `lightning-icon` with utility/standard sets; custom SVG as a static resource.
- Never hardcode labels — use **Custom Labels** (`@salesforce/label/c.My_Label`) for i18n.

## 6. Accessibility

- Base components handle most a11y; when using raw HTML, add `aria-*`, `role`, focus management, and keyboard handlers.
- Every icon-only button has `alternative-text`; every form input a `label` (or `variant="label-hidden"` with `label` set).
- Test with the **sa11y** Jest matcher and screen reader on at least one key flow.

## 7. Platform surfaces

- **Lightning App Builder / Record pages**: expose `@api recordId`, `@api objectApiName`; declare `targets` and `targetConfigs` in `js-meta.xml` with typed design properties and `default` values so admins configure without code.
- **Flow screens**: `lightning__FlowScreen` target; `@api` inputs/outputs; dispatch `FlowAttributeChangeEvent` on change and `FlowNavigationNextEvent` for navigation; validate via `@api validate()`.
- **Experience Cloud (LWR)**: no Aura, no `lightning/navigation` shortcuts that assume internal app; use `lightning__ServiceCloud`/`lightningCommunity__Page` targets, guest-user sharing considerations, `lightning/navigation` with `standard__webPage`; performance budget matters — LWR pages are public.
- **Quick actions**: `lightning__RecordAction` with `actionType="ScreenAction"`; close via `CloseActionScreenEvent`.
- **Utility bar / Console**: `lightning/platformWorkspaceApi` for tab management.
- **Mobile**: Salesforce Mobile App renders LWCs — test touch targets and offline behaviour with **LWC Offline** (Mobile Offline / Field Service) if relevant.

## 8. Security

- **Lightning Web Security (LWS)** is the runtime; avoid `eval`, `innerHTML` with user data, and dynamic `import()` of third-party code. Third-party libraries load from **static resources** via `loadScript`/`loadStyle` only.
- Trust nothing from `@api` inputs in a public Experience site — validate server-side.
- CRUD/FLS: enforced in Apex with USER_MODE; UI hides fields but never relies on hiding as security.
- CSP Trusted Sites for any external URL fetched from the browser.
- Never put secrets in JS or static resources; use Named Credentials in Apex.

## 9. Delivery

- Source-format project (`sfdx-project.json`), **scratch orgs** for dev, unlocked packages or CI deploy to sandboxes; `sf` CLI, not `sfdx`.
- ESLint with `@salesforce/eslint-config-lwc/recommended`; Prettier with the Apex plugin; Jest with `@salesforce/sfdx-lwc-jest`; PMD for Apex.
- Code review checklist: data-access decision justified, USER_MODE present, labels externalised, events named correctly, `renderedCallback` guarded, tests present.

## Anti-patterns to flag

- Apex for something `lightning-record-form` or `getRecord` does.
- `@wire` method that isn't `cacheable=true`, or cacheable methods doing DML.
- Imperative Apex on every keystroke without debounce.
- `renderedCallback` fetching data or mutating tracked state without a guard (infinite loop).
- Mutating arrays in place and wondering why the template didn't update.
- Global `window` state or pub/sub helpers instead of LMS.
- Custom-built inputs/buttons duplicating base components (and losing a11y).
- SOQL in a loop or unbulkified Apex serving a datatable.
- Hardcoded record type ids, profile names, or labels.
- Aura components for new work.
