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).
When agents use itUse 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.
Install
Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.
npx skills add AGCO-Global/org-skills --skill lwc-development -a github-copilot
# or with the org installer (adds .github/skills/lwc-development):
npx -y github:AGCO-Global/org-skills add skill lwc-development
Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.
Installs the salesforce-lwc-skills plugin, which bundles all Salesforce / LWC skills and keeps them updated.
/plugin marketplace add AGCO-Global/org-skills
/plugin install salesforce-lwc-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill lwc-development -a claude-code
Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.
Installs into .agents/skills/, which Codex reads.
npx skills add AGCO-Global/org-skills --skill lwc-development -a codex
Installs into Cursor's skills folder.
npx skills add AGCO-Global/org-skills --skill lwc-development -a cursor
Installs into Gemini CLI's skills folder.
npx skills add AGCO-Global/org-skills --skill lwc-development -a gemini-cli
Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.
Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.
Skill contents
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.
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.
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.
---
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.