LWC Coding Standards
Author: callinj
Name: Joe Callin
Title: Sr. Salesforce Developer
Email: joe@jcallin.dev
Table of Contents
- Introduction
- Naming Conventions
- Component Structure and Organization
- Property Decorators
- Public Methods
- Template Directives
- Slot Usage
- Wire Adapters
- Lightning Data Service
- Message Channels
- Navigation Patterns
- Lifecycle Hooks
- Event Handling
- Error Handling and User Feedback
- Computed Properties
- Async Patterns
- Accessibility (Section 508 Compliance)
- Component Metadata
- Import Organization
- Documentation
- CSS Standards
- Code Style and Formatting
- Code Complexity
- Performance Optimization
- Component Composition
- Testing Standards
- Security
- Browser Compatibility
- API Version Standards
- Code Maintenance
Introduction
Purpose
This document establishes comprehensive coding standards and best practices for Lightning Web Component (LWC) development within the Salesforce component library. These standards ensure consistency, maintainability, security, accessibility, and quality across all LWC code.
Scope
These standards apply to all Lightning Web Components, including JavaScript, HTML templates, SASS/SCSS stylesheets, and component metadata files within the component library. All code must comply with these standards and pass ESLint validation using the project's configuration.
Related Documentation
- Apex Coding Standards - Backend coding standards and patterns
- Code Analyzer Compliance - Detailed Code Analyzer rules and compliance requirements
- Testing Standards - Test coverage and testing patterns
- Git Branching Strategy - Git branching and merge strategy for the repository
- Component Library Modernization Plan - Overall project modernization strategy
Naming Conventions
Components
-
Format: camelCase
-
Naming: Descriptive camelCase name that clearly indicates the component's purpose
-
Pattern: Component folder and files use the same camelCase name
-
Examples:
// Component folder: sfcText/ // Files: sfcText.js, sfcText.html, sfcText.css (compiled), sfcText.js-meta.xml // Source: __styles__/sfcText.scss (compiles to sfcText.css) export default class SfcText extends LightningElement { } // Component folder: sfcAction/ // Files: sfcAction.js, sfcAction.html, sfcAction.css (compiled), sfcAction.js-meta.xml // Source: __styles__/sfcAction.scss (compiles to sfcAction.css) export default class SfcAction extends LightningElement { }
Properties
-
Format: camelCase
-
Naming: Descriptive names that indicate purpose
-
Public Properties: Use
@apidecorator -
Private Properties: Prefix with underscore (
_) for backing properties -
Private Property Scope: Properties prefixed with
_should only be referenced within the same JavaScript file. They must not be referenced in:- Component HTML templates
- Other JavaScript files
- Parent or child components
-
Examples:
// Public properties @api recordId; @api label = 'Click Me'; @api disabled = false; // Private backing properties (for getters/setters) // These should ONLY be used within this JS file _customClass = 'default-class'; _align = 'Left'; // Private reactive properties (no underscore - used in templates) actionClass = ''; isLoading = false;
Important: Underscore-prefixed properties (_propertyName) are implementation details and should never be accessed from templates or other files. Use getters/setters or public properties for template access.
Variables
-
Format: camelCase
-
Naming: Descriptive names that clearly indicate what the variable holds
-
Meaningful Names Required: Variable names must be meaningful and self-documenting
-
Single/Double Character Variables: Not allowed except for loop index variables (
i,j,k, etc.) -
Examples:
// Good: Meaningful variable names const accountName = 'Test Account'; const recordCount = 10; const isLoading = true; const buttonElement = this.template.querySelector('button'); const accountData = await fetchAccountData(); const errorMessage = error.body?.message || 'Unknown error'; // Good: Loop index variables (exception to the rule) for (let i = 0; i < items.length; i++) { processItem(items[i]); } for (let j = 0; j < rows.length; j++) { for (let k = 0; k < columns.length; k++) { processCell(rows[j], columns[k]); } } // Bad: Single/double character variables (except loop indices) const a = 'Test Account'; // Bad - use accountName const x = 10; // Bad - use recordCount const el = this.template.querySelector('button'); // Bad - use buttonElement const d = await fetchAccountData(); // Bad - use accountData // Bad: Unclear abbreviations const accNm = 'Test Account'; // Bad - use accountName const btn = this.template.querySelector('button'); // Bad - use buttonElement const err = error.message; // Bad - use errorMessage // Good: Meaningful names even in short scopes const selectedItem = items[selectedIndex]; const currentUser = getCurrentUser(); const formData = new FormData();
Best Practices:
- Use full words, not abbreviations (unless the abbreviation is widely understood)
- Be specific about what the variable contains (e.g.,
accountNamenotname,buttonElementnotelement) - Use descriptive names even for temporary variables
- Only use single-character variables (
i,j,k) for loop indices
Methods
-
Format: camelCase
-
Naming: Verb-based names that describe the action performed
-
Event Handlers: Prefix with
handle(e.g.,handleClick,handleChange) -
Private Methods: No special prefix required, but should be clear they're internal
-
Examples:
// Public methods @api focus() { this.template.querySelector('button').focus(); } // Event handlers handleClick() { // Handle click } handleInputChange(event) { // Handle input change } // Private methods setColorClasses() { // Set color classes } validateInput(input) { // Validate input }
Constants
-
Format: UPPER_SNAKE_CASE
-
Naming: Descriptive names in all caps with underscores
-
Location: Define at the top of the file, outside the class
-
Examples:
const DEFAULT_CLASS = 'sfc-component'; const ICON_CLASS = 'slds-button__icon slds-current-color'; const DEFAULT_COPY_TEXT = 'URL has been copied to your clipboard.'; const MAX_RETRY_ATTEMPTS = 3;
CSS Classes
-
Format: kebab-case or BEM methodology
-
Naming: Use SLDS classes when possible, custom classes should be descriptive
-
Examples:
/* SLDS classes */ .slds-button .slds-button_brand .slds-text-align_center /* Custom classes */ .sfc-component .sfc-action-custom-color .sfc-text-heading
CSS Custom Properties (Variables)
-
Format: kebab-case with component prefix
-
Naming: Use component prefix followed by descriptive name
-
Examples:
:host { --sfc-action-color-background: #0070d2; --sfc-action-color-border: #0070d2; --sfc-text-font-size: 1rem; --sfc-text-font-weight: 700; }
Component Structure and Organization
File Organization
Each component must consist of the following files in a single folder:
componentName.js- Component JavaScript classcomponentName.html- Component templatecomponentName.css- Compiled CSS file (generated from SASS/SCSS)componentName.js-meta.xml- Component metadata__styles__/- Folder containing SASS/SCSS source files
Style Files:
- Use SASS/SCSS for component stylesheets (source files)
- SASS/SCSS source files should be placed in a
__styles__folder within the component directory - The main SASS/SCSS file should be named
componentName.scssorcomponentName.sass - Additional SASS files (mixins, variables, etc.) can be organized as partials within the
__styles__folder - SASS/SCSS files compile to CSS files that are stored in the main component directory (same level as
.js,.html,.js-meta.xml) - The compiled CSS file (
componentName.css) is what LWC uses at runtime
Example:
src/components/lwc/sfcText/
├── sfcText.js
├── sfcText.html
├── sfcText.css (compiled from __styles__/sfcText.scss)
├── sfcText.js-meta.xml
└── __styles__
├── sfcText.scss (SASS source)
├── _variables.scss (SASS partial)
└── _mixins.scss (SASS partial)
Alternative Simple Structure:
src/components/lwc/sfcText/
├── sfcText.js
├── sfcText.html
├── sfcText.css (compiled from __styles__/sfcText.scss)
├── sfcText.js-meta.xml
└── __styles__
└── sfcText.scss (SASS source)
Note: The componentName.css file is generated during the build/compilation process from the SASS/SCSS files in the __styles__ folder. Developers should edit the SASS/SCSS source files, not the compiled CSS files directly.
Component Class Structure Order
Components should follow this order:
- Imports - All import statements
- Constants - Constants defined outside the class
- Class Declaration - Class definition extending LightningElement
- Private Properties - Private backing properties (prefixed with
_) - ONLY used within this JS file, never in templates - Private Reactive Properties - Private reactive properties (can be used in templates)
- Computed Properties (Getters) - Getter methods
- Public Properties -
@apiproperties - Public Methods -
@apimethods - Wire Adapters -
@wireproperties - Lifecycle Hooks -
connectedCallback,renderedCallback,disconnectedCallback,errorCallback - Event Handlers - Event handler methods
- Private Methods - Private helper methods
Example:
import { LightningElement, api } from 'lwc';
import { isEmpty } from 'c/sfcUtils';
const DEFAULT_CLASS = 'sfc-component';
export default class SfcText extends LightningElement {
// 1. Private Properties
// Note: These underscore-prefixed properties should ONLY be used within this JS file
// They must NOT be referenced in templates or other files
_customClass = DEFAULT_CLASS;
_align = 'Left';
// 2. Private Reactive Properties
// Note: These properties (no underscore) can be used in templates
contentClass = '';
textClass = 'sfc-component';
isLoading = false;
// 3. Computed Properties (Getters)
get isHeading() {
return !this.type.includes('p');
}
// Good: Private property accessed within same file (via getter)
get align() {
return this._align?.toLowerCase() || 'left';
}
// 4. Public Properties
@api recordId;
@api label = 'Default Label';
@api get customClass() {
return this._customClass;
}
set customClass(value) {
this._customClass = value || DEFAULT_CLASS;
}
// 5. Public Methods
@api focus() {
const button = this.template.querySelector('button');
if (button) {
button.focus();
}
}
// 6. Lifecycle Hooks
connectedCallback() {
this.classList.add(...this.customClass.split(' '));
}
renderedCallback() {
// DOM manipulation if needed
}
disconnectedCallback() {
// Cleanup if needed
}
// 7. Event Handlers
handleClick() {
this.dispatchEvent(new CustomEvent('click'));
}
// 8. Private Methods
setColorClasses() {
// Set color classes
}
}
Package Organization
Code is organized in feature-based directories:
src/
├── utilities/
│ └── classes/
├── base/
│ └── classes/
└── components/
└── lwc/
├── text/
├── action/
└── background/
Property Decorators
Private Property Conventions: _ vs #
Current Standard: Underscore Prefix (_)
We currently use the underscore prefix (_) convention for private backing properties. This is a widely adopted convention in JavaScript and the Salesforce LWC ecosystem.
JavaScript Private Fields: Hash Symbol (#)
JavaScript ES2022 introduced true private class fields using the hash symbol (#), which provides language-level privacy enforcement. Unlike the underscore convention, # private fields cannot be accessed from outside the class, even accidentally.
Comparison:
| Feature | Underscore (_) |
Hash (#) |
|---|---|---|
| Privacy Enforcement | Convention only (not enforced) | Language-level enforcement |
| Browser Support | All browsers | Modern browsers (ES2022+) |
| Salesforce LWC Support | Fully supported | Needs verification |
| Backward Compatibility | Yes | No (requires modern JavaScript) |
| Common Usage | Very common in LWC | Less common in Salesforce ecosystem |
Current Recommendation:
-
Continue using underscore (
_) convention for now, as it's:- Widely supported across all Salesforce LWC environments
- Well-understood by the Salesforce developer community
- Consistent with existing codebase patterns
- Works in all supported browsers
-
Verify Salesforce LWC support for
#syntax before considering migration:- Check Salesforce LWC documentation for ES2022 private fields support
- Verify compatibility with all target Salesforce environments
- Test in sandbox environments before adopting
-
If
#is supported and verified, consider migrating to#for:- True privacy enforcement
- Alignment with modern JavaScript standards
- Better encapsulation
Important: Regardless of which convention is used (_ or #), private properties should only be referenced within the same JavaScript file and never in templates or other files.
Example with Underscore (Current Standard):
export default class SfcText extends LightningElement {
// Private backing property - only used in this file
_customClass = 'default-class';
// Public API - can be used in templates and by parent components
@api get customClass() {
return this._customClass;
}
set customClass(value) {
this._customClass = value || 'default-class';
}
}
Example with Hash (If Supported):
export default class SfcText extends LightningElement {
// Private field - truly private, cannot be accessed outside class
#customClass = 'default-class';
// Public API - can be used in templates and by parent components
@api get customClass() {
return this.#customClass;
}
set customClass(value) {
this.#customClass = value || 'default-class';
}
}
Future Consideration:
This standard should be reviewed and updated if/when Salesforce LWC officially supports and recommends the # syntax for private fields. Until then, the underscore convention remains the standard.
@api Decorator
Usage: Use @api to expose public properties and methods that can be set by a parent component or used in the component configuration.
Guidelines:
- Use
@apifor properties that need to be configurable from outside the component - Use
@apifor methods that need to be callable from parent components - Properties decorated with
@apiare reactive by default - Use getters/setters when transformation or validation is needed
Direct @api Properties:
Use direct @api properties when no transformation or validation is needed:
// Good: Simple property, no transformation needed
@api recordId;
@api label = 'Default Label';
@api disabled = false;
@api iconName;
@api Getters/Setters:
Use getters/setters when you need to transform, validate, or perform side effects:
// Good: Transformation needed
// Note: _customClass is private - only accessed within this file
@api get customClass() {
return this._customClass;
}
set customClass(value) {
this._customClass = value || DEFAULT_CLASS;
this.classList.add(...this._customClass.split(' '));
}
// Good: Validation needed
// Note: _borderRadius is private - only accessed within this file
@api get borderRadius() {
return this._borderRadius;
}
set borderRadius(value) {
if (value && isValidStyle(value, 'borderRadius')) {
this._cssVariables['--sfc-action-border-radius'] = value;
}
this._borderRadius = value;
}
Important: The underscore-prefixed properties (_customClass, _borderRadius) are private implementation details. They should never be referenced in:
- Component HTML templates (use the public getter/setter instead)
- Other JavaScript files
- Parent or child components
Bad Example (Accessing Private Property in Template):
<!-- Bad: Accessing private property directly -->
<div class={_customClass}>Content</div>
// Bad: Private property accessed in template
// This violates encapsulation
Good Example (Using Public API in Template):
<!-- Good: Using public property/getter -->
<div class={customClass}>Content</div>
// Good: Template uses public API, private property stays internal
@api get customClass() {
return this._customClass;
}
@track Decorator
Usage: Use @track for private properties that need to trigger re-rendering when changed. In modern LWC (API 40+), most properties are reactive by default, so @track is rarely needed.
Guidelines:
- Properties are reactive by default in API 40+
- Use
@trackonly when you need to track changes to object properties or array elements - Prefer creating new objects/arrays rather than mutating existing ones
When to Use @track:
// Good: Track object property changes
@track config = {
enabled: true,
size: 'medium'
};
// Good: Track array element changes (though creating new array is preferred)
@track items = [];
// Usually not needed - properties are reactive by default
// @track isLoading = false; // Unnecessary in API 40+
Preferred Pattern (Creating New Objects):
// Good: Create new object instead of mutating
updateConfig(newValue) {
this.config = {
...this.config,
enabled: newValue
};
}
// Good: Create new array instead of mutating
addItem(item) {
this.items = [...this.items, item];
}
@wire Decorator
Usage: Use @wire to read Salesforce data reactively. The wire service provides data to the component automatically.
Guidelines:
- Use
@wirefor reactive data that should update automatically - Wire adapters include:
getRecord,getRecordUi,getObjectInfo,getPicklistValues, Apex methods, and message channels - Wire properties are read-only - don't assign to them directly
- Handle wire errors appropriately
Examples:
import { wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import { MessageContext } from 'lightning/messageService';
import getAccountData from '@salesforce/apex/AccountController.getAccountData';
// Wire to Lightning Data Service
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.account = undefined;
}
}
// Wire to Apex method
@wire(getAccountData, { accountId: '$recordId' })
wiredAccountData({ error, data }) {
if (data) {
this.accountData = data;
} else if (error) {
this.handleError(error);
}
}
// Wire to Message Channel
@wire(MessageContext)
messageContext;
See: Wire Adapters for detailed patterns
Tracked vs Reactive Properties
Reactive Properties (Default):
Most properties are reactive by default in API 40+:
// These are reactive automatically
@api recordId;
label = 'Default';
isLoading = false;
items = [];
When Properties Are Reactive:
- Properties decorated with
@api - Properties in the class body (not in methods)
- Properties accessed in the template
When Properties Are NOT Reactive:
- Properties defined inside methods
- Static properties
- Properties not accessed in the template
Best Practice:
- Prefer creating new objects/arrays over mutating existing ones
- Use
@trackonly when you need to track deep object/array changes - Most use cases don't require
@trackin modern LWC
Public Methods
When to Expose Public Methods
Expose @api methods when:
- Parent components need to control child behavior - Focus, click, reset, etc.
- Component needs to be controlled externally - Show/hide, enable/disable programmatically
- Utility methods that parent components need - Validation, data retrieval, etc.
Examples:
// Good: Focus method for parent to control focus
@api focus() {
const button = this.template.querySelector('button');
if (button) {
button.focus();
}
}
// Good: Click method for parent to trigger click
@api click() {
const button = this.template.querySelector('button');
if (button) {
button.click();
}
}
// Good: Reset method for parent to reset component state
@api reset() {
this.value = '';
this.error = undefined;
this.isDirty = false;
}
// Good: Validation method for parent to check validity
@api validate() {
if (!this.value) {
this.error = 'Value is required';
return false;
}
this.error = undefined;
return true;
}
Public Method Patterns
When Connection Checks Are Needed:
For synchronous public @api methods, connection checks are typically not necessary because:
- Parent components can only call child methods when the child is connected
- LWC ensures components are connected before they can be interacted with
- Event handlers are only called when components are connected
Connection checks ARE needed for:
- Async operations - Promises, setTimeout, setInterval callbacks that might complete after disconnection
- Wire adapter callbacks - Can sometimes fire after disconnection in edge cases (though LWC usually handles this)
Examples:
// Not needed: Synchronous public method (component is inherently connected)
@api focus() {
const button = this.template.querySelector('button');
if (button) { // Check element existence, not connection
button.focus();
}
}
// Not needed: Event handler (component is inherently connected)
handleClick() {
const button = this.template.querySelector('button');
if (button) { // Check element existence, not connection
button.focus();
}
}
// Needed: Async operation that might complete after disconnection
async loadData() {
const data = await fetchData();
if (this._connected) { // Check needed - component might be disconnected
this.data = data;
}
}
// Needed: setTimeout callback
setTimeout(() => {
if (this._connected) { // Check needed - component might be disconnected
this.updateUI();
}
}, 1000);
// Needed: Promise callback
fetchData()
.then((data) => {
if (this._connected) { // Check needed - component might be disconnected
this.data = data;
}
});
// Track connection state (only needed if you use connection checks)
connectedCallback() {
this._connected = true;
}
disconnectedCallback() {
this._connected = false;
}
Best Practice:
- Don't check connection for synchronous public methods and event handlers
- Do check connection for async operations (promises, timers, intervals)
- Always check element existence before accessing DOM elements (regardless of connection check)
Return Values:
Public methods can return values for parent components:
// Good: Return validation result
@api validate() {
if (!this.value) {
return { isValid: false, error: 'Value is required' };
}
return { isValid: true, error: undefined };
}
// Good: Return current state
@api getState() {
return {
value: this.value,
isValid: this.isValid,
isDirty: this.isDirty
};
}
Public Method Naming
- Use verb-based names:
focus(),click(),reset(),validate(),getState() - Be descriptive about what the method does
- Avoid generic names like
doSomething()orhandle()
Template Directives
Template Expression Rules
Important: Brackets ({}) in templates can only reference variables, properties, or getters. Logic and expressions are not allowed in brackets.
Guidelines:
- Allowed: Property references, getter calls, method calls (no parameters)
- Not Allowed: Comparison operators (
===,!==,>,<), logical operators (&&,||), arithmetic operations, ternary operators
Examples:
<!-- Good: Reference property directly -->
<div>{accountName}</div>
<lightning-button lwc:if={isVisible} label="Click Me"></lightning-button>
<!-- Good: Reference getter (getter contains the logic) -->
<!-- JavaScript: get isSuccess() { return this.status === 'success'; } -->
<div lwc:if={isSuccess}>Success</div>
<!-- Good: Reference method call -->
<div>{getDisplayValue()}</div>
<!-- Bad: Logic in brackets (not allowed) -->
<div lwc:if={status === 'success'}>Success</div>
<div lwc:if={items.length > 0}>Has items</div>
<div>{isVisible && accountName}</div>
<div>{count + 1}</div>
Solution: Use getters for computed conditions:
// Bad: Logic in template
// Template: <div lwc:if={status === 'success'}>Success</div>
// Good: Logic in getter
get isSuccess() {
return this.status === 'success';
}
// Template: <div lwc:if={isSuccess}>Success</div>
lwc:if
Usage: Use lwc:if for conditional rendering. This is the modern, preferred approach over if:true/if:false.
Migration Requirement: Any existing if:true/if:false should be migrated to lwc:if when touching the component.
Guidelines:
- Single element: When controlling a single element, place
lwc:ifdirectly on that element - Multiple elements: When controlling multiple elements, wrap them in a
<template>tag withlwc:if - No logic in brackets: Brackets can only reference variables/properties, not expressions. Use getters for computed conditions
Examples:
<!-- Good: Single element - lwc:if on the element itself -->
<lightning-button lwc:if={isVisible} label="Click Me"></lightning-button>
<!-- Good: Single element with lwc:if -->
<div lwc:if={isLoading} class="loading">Loading...</div>
<!-- Good: Multiple elements - wrap in template -->
<template lwc:if={isVisible}>
<div>Content is visible</div>
<p>Additional content</p>
</template>
<!-- Good: Use lwc:else for alternative content -->
<lightning-spinner lwc:if={isLoading}></lightning-spinner>
<template lwc:else>
<div>Content loaded</div>
</template>
<!-- Good: Multiple conditions using getters (no logic in brackets) -->
<!-- JavaScript: get isSuccess() { return this.status === 'success'; } -->
<div lwc:if={isSuccess} class="success">Success message</div>
<!-- JavaScript: get isError() { return this.status === 'error'; } -->
<div lwc:if={isError} class="error">Error message</div>
<!-- JavaScript: get isDefault() { return !this.isSuccess && !this.isError; } -->
<div lwc:if={isDefault}>Default message</div>
<!-- Bad: Logic in brackets (not allowed) -->
<div lwc:if={status === 'success'}>Success</div>
<!-- Bad: Wrapping single element in template when not needed -->
<template lwc:if={isVisible}>
<lightning-button label="Click Me"></lightning-button>
</template>
<!-- Bad: Old if:true syntax (should be migrated) -->
<div if:true={isVisible}>Content</div>
Performance Note: lwc:if removes elements from the DOM when false, which is more efficient than hiding with CSS.
lwc:for
Usage: Use lwc:for to iterate over arrays and render lists.
Guidelines:
- Always use
lwc:keywithlwc:forfor performance and correct rendering - Use unique, stable keys (prefer IDs over array indices when possible)
- Handle empty arrays appropriately
Examples:
<!-- Good: Use lwc:for with lwc:key -->
<template lwc:for={items} lwc:key={item.id}>
<div>{item.name}</div>
</template>
<!-- Good: Use index as key only when items don't have unique IDs -->
<template lwc:for={items} lwc:key={index}>
<div>{item.name}</div>
</template>
<!-- Good: Handle empty arrays using getter -->
<!-- JavaScript: get hasItems() { return this.items && this.items.length > 0; } -->
<template lwc:if={hasItems}>
<template lwc:for={items} lwc:key={item.id}>
<div>{item.name}</div>
</template>
</template>
<template lwc:else>
<div>No items found</div>
</template>
<!-- Bad: Logic in brackets (not allowed) -->
<template lwc:if={items.length > 0}>
<template lwc:for={items} lwc:key={item.id}>
<div>{item.name}</div>
</template>
</template>
lwc:key
Usage: Always use lwc:key with lwc:for to help LWC track list items efficiently.
Guidelines:
- Use unique, stable identifiers (prefer record IDs over indices)
- Keys should remain the same for the same item across re-renders
- Don't use array indices as keys if items can be reordered
Examples:
// Good: Use unique ID as key
items = [
{ id: '1', name: 'Item 1' },
{ id: '2', name: 'Item 2' }
];
// In template:
<template lwc:for={items} lwc:key={item.id}>
<div>{item.name}</div>
</template>
// Acceptable: Use index only when items are static and won't be reordered
<template lwc:for={items} lwc:key={index}>
<div>{item.name}</div>
</template>
lwc:ref
Usage: Use lwc:ref to get a reference to a DOM element for programmatic access.
Guidelines:
- Use
lwc:refinstead ofquerySelectorwhen possible for better performance - Access refs in
renderedCallbackor after component is rendered - Check if ref exists before using it
Examples:
<!-- Good: Use lwc:ref for element reference -->
<button lwc:ref="actionButton" onclick={handleClick}>
Click Me
</button>
// Good: Access ref in renderedCallback
renderedCallback() {
const button = this.refs.actionButton;
if (button) {
button.focus();
}
}
// Good: Access ref in method after rendering
@api focus() {
const button = this.refs.actionButton;
if (button) {
button.focus();
}
}
lwc:else and lwc:elseif
Usage: Use lwc:else and lwc:elseif with lwc:if for alternative content rendering.
Guidelines:
- No logic in brackets: Use getters for computed conditions, not expressions in brackets
- Single element: When controlling a single element, place
lwc:ifdirectly on that element - Multiple elements: Wrap multiple elements in
<template>tags
Examples:
<!-- Good: Single element with lwc:else -->
<lightning-spinner lwc:if={isLoading}></lightning-spinner>
<template lwc:else>
<div>Content loaded</div>
</template>
<!-- Good: Multiple elements with lwc:else -->
<template lwc:if={isLoading}>
<lightning-spinner></lightning-spinner>
<div>Please wait...</div>
</template>
<template lwc:else>
<div>Content loaded</div>
</template>
<!-- Good: Multiple conditions using getters (no logic in brackets) -->
<!-- JavaScript: get isSuccess() { return this.status === 'success'; } -->
<!-- JavaScript: get isError() { return this.status === 'error'; } -->
<div lwc:if={isSuccess} class="success">Success</div>
<div lwc:if={isError} class="error">Error</div>
<!-- JavaScript: get isDefault() { return !this.isSuccess && !this.isError; } -->
<div lwc:if={isDefault}>Default</div>
<!-- Bad: Logic in brackets (not allowed) -->
<template lwc:if={status === 'success'}>
<div class="success">Success</div>
</template>
<template lwc:elseif={status === 'error'}>
<div class="error">Error</div>
</template>
lwc:dom="manual"
Usage: Use lwc:dom="manual" when you need to manually manipulate the DOM, bypassing LWC's rendering system.
Guidelines:
- Use sparingly - prefer declarative templates when possible
- Must manually handle DOM updates, including cleanup
- Typically used for third-party libraries or complex DOM manipulation
Examples:
<!-- Good: Manual DOM manipulation for third-party library -->
<div lwc:dom="manual"></div>
// Good: Manual DOM manipulation
renderedCallback() {
if (this._initialized) {
return;
}
const container = this.template.querySelector('.chart-container');
if (container) {
// Initialize third-party chart library
this.chart = new Chart(container, this.chartConfig);
this._initialized = true;
}
}
disconnectedCallback() {
// Cleanup manual DOM manipulation
if (this.chart) {
this.chart.destroy();
this.chart = null;
}
}
Slot Usage
Default Slots
Usage: Use default slots to allow parent components to inject content into child components.
Examples:
<!-- Child component template -->
<template>
<div class="card">
<div class="card-header">
<slot></slot>
</div>
</div>
</template>
<!-- Parent component usage -->
<c-sfc-card>
<h2>Card Title</h2>
<p>Card content</p>
</c-sfc-card>
Named Slots
Usage: Use named slots when you need multiple injection points in a component.
Examples:
<!-- Child component template -->
<template>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot name="body"></slot>
</div>
<div class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<!-- Parent component usage -->
<c-sfc-card>
<span slot="header">Header Content</span>
<span slot="body">Body Content</span>
<span slot="footer">Footer Content</span>
</c-sfc-card>
Slot Fallback Content
Usage: Provide fallback content that displays when no content is provided to the slot.
Examples:
<!-- Good: Provide fallback content -->
<template>
<div class="card">
<div class="card-header">
<slot>
<h2>Default Title</h2>
</slot>
</div>
</div>
</template>
<!-- Good: Named slot with fallback -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">
<h2>Default Header</h2>
</slot>
</div>
</div>
</template>
Slot Best Practices
- Use slots to make components flexible and reusable
- Provide meaningful fallback content when appropriate
- Use named slots for complex layouts with multiple content areas
- Document slot usage in component documentation
Wire Adapters
getRecord
Usage: Use getRecord to fetch a single record's data from Lightning Data Service.
Guidelines:
- Define fields as constants at the top of the file
- Handle both data and error cases
- Use reactive parameters with
$prefix for dynamic updates
Examples:
import { wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
const ACCOUNT_FIELDS = [
'Account.Id',
'Account.Name',
'Account.Industry',
'Account.AnnualRevenue'
];
export default class AccountDetail extends LightningElement {
@api recordId;
account;
error;
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.account = undefined;
this.handleError(error);
}
}
handleError(error) {
// Handle error appropriately
console.error('Error loading account:', error);
}
}
getRecordUi
Usage: Use getRecordUi to fetch record data with UI metadata (layout, picklist values, etc.).
Examples:
import { wire } from 'lwc';
import { getRecordUi } from 'lightning/uiRecordApi';
export default class AccountForm extends LightningElement {
@api recordId;
recordUi;
error;
@wire(getRecordUi, { recordIds: '$recordId', layoutTypes: 'Full', modes: 'View' })
wiredRecordUi({ error, data }) {
if (data) {
this.recordUi = data.records[this.recordId];
this.error = undefined;
} else if (error) {
this.error = error;
this.handleError(error);
}
}
}
Wire to Apex Methods
Usage: Use @wire with Apex methods for reactive data fetching.
Guidelines:
- Use
cacheable=truefor read-only methods when possible - Handle errors appropriately
- Use reactive parameters with
$prefix
Examples:
import { wire } from 'lwc';
import getAccountData from '@salesforce/apex/AccountController.getAccountData';
export default class AccountData extends LightningElement {
@api recordId;
accountData;
error;
@wire(getAccountData, { accountId: '$recordId' })
wiredAccountData({ error, data }) {
if (data) {
this.accountData = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.handleError(error);
}
}
handleError(error) {
// Handle error - show toast, log, etc.
console.error('Error loading account data:', error);
}
}
Apex Method (for reference):
@AuraEnabled(cacheable=true)
public static Account getAccountData(Id accountId) {
return [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];
}
Wire to Message Channels
Usage: Use @wire with MessageContext to subscribe to message channels.
Examples:
import { wire } from 'lwc';
import { MessageContext } from 'lightning/messageService';
import SELECTED_CHANNEL from '@salesforce/messageChannel/sfcAction__c';
export default class MessageSubscriber extends LightningElement {
@wire(MessageContext)
messageContext;
connectedCallback() {
// Subscribe to messages
this.subscribe();
}
subscribe() {
if (this.messageContext) {
this.subscription = subscribe(
this.messageContext,
SELECTED_CHANNEL,
(message) => this.handleMessage(message)
);
}
}
handleMessage(message) {
// Handle received message
console.log('Received message:', message);
}
disconnectedCallback() {
// Unsubscribe from messages
if (this.subscription) {
unsubscribe(this.subscription);
}
}
}
Wire Error Handling
Guidelines:
- Always handle wire errors appropriately
- Provide user feedback for errors (toast notifications, error states)
- Log errors for debugging
- Don't leave error states unhandled
Examples:
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
this.isLoading = false;
} else if (error) {
this.error = error;
this.account = undefined;
this.isLoading = false;
this.handleWireError(error);
}
}
handleWireError(error) {
// Show toast notification
this.showToast('Error', 'Failed to load account data', 'error');
// Log error for debugging
console.error('Wire error:', error);
// Set error state for UI
this.hasError = true;
}
Lightning Data Service
getRecord Pattern
Usage: Use getRecord for fetching single record data with field-level security and sharing rules automatically enforced.
Best Practices:
- Define fields as constants
- Use reactive parameters
- Handle errors appropriately
- Access fields using
data.fields.FieldName.value
Examples:
import { wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
const ACCOUNT_FIELDS = [
'Account.Id',
'Account.Name',
'Account.Industry',
'Account.AnnualRevenue'
];
export default class AccountDetail extends LightningElement {
@api recordId;
accountId;
accountName;
industry;
annualRevenue;
error;
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.accountId = data.id;
this.accountName = data.fields.Name.value;
this.industry = data.fields.Industry.value;
this.annualRevenue = data.fields.AnnualRevenue.value;
this.error = undefined;
} else if (error) {
this.error = error;
this.handleError(error);
}
}
}
getRecordUi Pattern
Usage: Use getRecordUi when you need record data along with UI metadata (layouts, picklist values, etc.).
Examples:
import { wire } from 'lwc';
import { getRecordUi } from 'lightning/uiRecordApi';
export default class AccountForm extends LightningElement {
@api recordId;
recordUi;
recordData;
layout;
picklistValues;
error;
@wire(getRecordUi, {
recordIds: '$recordId',
layoutTypes: 'Full',
modes: 'View'
})
wiredRecordUi({ error, data }) {
if (data) {
this.recordUi = data.records[this.recordId];
this.recordData = this.recordUi.fields;
this.layout = this.recordUi.layout;
this.picklistValues = this.recordUi.picklistValues;
this.error = undefined;
} else if (error) {
this.error = error;
this.handleError(error);
}
}
}
Field Access Patterns
Accessing Field Values:
// Good: Access field value with null check
if (data && data.fields && data.fields.Name) {
this.accountName = data.fields.Name.value;
}
// Good: Use optional chaining (if supported)
this.accountName = data?.fields?.Name?.value;
// Good: Provide default value
this.accountName = data?.fields?.Name?.value || 'Unknown';
Message Channels
MessageContext Pattern
Usage: Use MessageContext with @wire to access the Lightning Message Service context.
Examples:
import { wire } from 'lwc';
import { MessageContext } from 'lightning/messageService';
export default class MessageComponent extends LightningElement {
@wire(MessageContext)
messageContext;
}
Publishing Messages
Usage: Use publish to send messages to subscribed components.
Examples:
import { publish } from 'lightning/messageService';
import SELECTED_CHANNEL from '@salesforce/messageChannel/sfcAction__c';
export default class MessagePublisher extends LightningElement {
@wire(MessageContext)
messageContext;
handleClick() {
if (this.messageContext) {
publish(this.messageContext, SELECTED_CHANNEL, {
identifier: this.uniqueId,
action: 'clicked'
});
}
}
}
Subscribing to Messages
Usage: Use subscribe to listen for messages from other components.
Examples:
import { subscribe, unsubscribe } from 'lightning/messageService';
import SELECTED_CHANNEL from '@salesforce/messageChannel/sfcAction__c';
export default class MessageSubscriber extends LightningElement {
@wire(MessageContext)
messageContext;
subscription;
connectedCallback() {
this.subscribeToMessage();
}
subscribeToMessage() {
if (this.messageContext) {
this.subscription = subscribe(
this.messageContext,
SELECTED_CHANNEL,
(message) => this.handleMessage(message)
);
}
}
handleMessage(message) {
// Handle received message
if (message.identifier === this.targetId) {
this.handleAction(message.action);
}
}
disconnectedCallback() {
if (this.subscription) {
unsubscribe(this.subscription);
}
}
}
Message Channel Best Practices
- Always unsubscribe in
disconnectedCallbackto prevent memory leaks - Check if
messageContextexists before publishing/subscribing - Use meaningful message payloads with clear structure
- Document message channel usage in component documentation
Navigation Patterns
NavigationMixin Usage
Usage: Use NavigationMixin to navigate to different pages in Salesforce.
Examples:
import { NavigationMixin } from 'lightning/navigation';
export default class NavigationComponent extends NavigationMixin(LightningElement) {
handleNavigateToRecord() {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
}
handleNavigateToObjectHome() {
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: 'Account',
actionName: 'home'
}
});
}
handleNavigateToWebPage() {
this[NavigationMixin.Navigate]({
type: 'standard__webPage',
attributes: {
url: 'https://example.com'
}
});
}
handleGenerateUrl() {
this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
}).then((url) => {
// Use generated URL
window.open(url, '_blank');
});
}
}
Navigation Types
Common Navigation Types:
standard__recordPage- Navigate to a record detail pagestandard__objectPage- Navigate to an object's home/list viewstandard__webPage- Navigate to an external URLstandard__namedPage- Navigate to a named pagestandard__home- Navigate to the home page
Examples:
// Navigate to record page
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: '001xx000003DGbQAAW',
actionName: 'view'
}
});
// Navigate to object home
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: 'Account',
actionName: 'home'
}
});
// Navigate to external URL
this[NavigationMixin.Navigate]({
type: 'standard__webPage',
attributes: {
url: 'https://example.com'
}
});
Navigation Best Practices
- Use
NavigationMixinfor all navigation within Salesforce - Use
GenerateUrlwhen you need the URL for external use (e.g., opening in new tab) - Handle navigation errors appropriately
- Provide user feedback for navigation actions
Lifecycle Hooks
connectedCallback
Usage: Use connectedCallback to initialize the component when it's inserted into the DOM.
Guidelines:
- Initialize component state
- Set up event listeners (if needed)
- Subscribe to message channels
- Don't perform heavy operations - use wire adapters or async methods instead
Examples:
export default class MyComponent extends LightningElement {
_connected = false;
connectedCallback() {
this._connected = true;
this.classList.add('my-component');
this.initializeComponent();
}
initializeComponent() {
// Light initialization
this.setupEventListeners();
}
setupEventListeners() {
// Set up event listeners if needed
}
}
renderedCallback
Usage: Use renderedCallback to perform DOM manipulation after the component renders.
Guidelines:
- Use sparingly - prefer declarative templates when possible
- Check if work has already been done to avoid infinite loops
- Access DOM elements using
this.template.querySelectororlwc:ref - Don't modify component properties that trigger re-rendering
Examples:
export default class MyComponent extends LightningElement {
_initialized = false;
renderedCallback() {
// Check if already initialized to avoid infinite loops
if (this._initialized) {
return;
}
const button = this.template.querySelector('button');
if (button) {
// Perform DOM manipulation
button.classList.add('custom-class');
this._initialized = true;
}
}
}
Common Use Cases:
- Setting focus on elements
- Initializing third-party libraries
- Measuring DOM elements
- Setting CSS custom properties dynamically
disconnectedCallback
Usage: Use disconnectedCallback to clean up when the component is removed from the DOM.
Guidelines:
- Unsubscribe from message channels
- Remove event listeners
- Clean up timers/intervals
- Cancel pending promises/requests if possible
Examples:
export default class MyComponent extends LightningElement {
subscription;
intervalId;
connectedCallback() {
this.subscribeToMessages();
this.startInterval();
}
disconnectedCallback() {
// Unsubscribe from messages
if (this.subscription) {
unsubscribe(this.subscription);
}
// Clear interval
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
errorCallback
Usage: Use errorCallback to handle errors thrown during rendering, lifecycle hooks, or event handlers.
Guidelines:
- Log errors appropriately
- Provide user feedback
- Prevent error propagation if possible
- Set error state for UI
Examples:
export default class MyComponent extends LightningElement {
error;
errorCallback(error, stack) {
// Log error
console.error('Component error:', error);
console.error('Stack trace:', stack);
// Set error state
this.error = error;
// Show user-friendly error message
this.showErrorToast('An error occurred. Please try again.');
}
showErrorToast(title, message) {
// Show toast notification
}
}
Lifecycle Hook Best Practices
- Keep lifecycle hooks focused and lightweight
- Avoid heavy operations in
connectedCallback - Use flags to prevent infinite loops in
renderedCallback - Always clean up in
disconnectedCallback - Handle errors appropriately in
errorCallback
Event Handling
Custom Events
Usage: Use CustomEvent to communicate from child to parent components.
Guidelines:
- Use descriptive event names
- Include relevant data in
detailproperty - Use
bubbles: trueandcomposable: truefor standard events - Follow event naming conventions
Examples:
// Good: Dispatch custom event with data
handleClick() {
const event = new CustomEvent('actionclicked', {
detail: {
recordId: this.recordId,
action: 'clicked',
timestamp: Date.now()
},
bubbles: true,
composed: true
});
this.dispatchEvent(event);
}
// Good: Simple event without data
handleChange() {
this.dispatchEvent(new CustomEvent('change', {
bubbles: true,
composed: true
}));
}
Parent Component Usage:
<!-- Parent component template -->
<template>
<c-child-component onactionclicked={handleActionClicked}></c-child-component>
</template>
// Parent component JavaScript
handleActionClicked(event) {
const { recordId, action } = event.detail;
// Handle event
}
Standard Events
Usage: Use standard DOM events when appropriate (click, change, input, etc.).
Examples:
// Good: Use standard click event
handleClick(event) {
// Handle click
this.dispatchEvent(new CustomEvent('click', {
bubbles: true,
composed: true
}));
}
// Good: Use standard change event
handleInputChange(event) {
const value = event.target.value;
this.dispatchEvent(new CustomEvent('change', {
detail: { value },
bubbles: true,
composed: true
}));
}
Event Naming Conventions
Guidelines:
- Use camelCase for event names
- Be descriptive about what triggered the event
- Use verb + past tense for action events:
clicked,changed,selected - Use noun + verb for state events:
loadstarted,loadcompleted
Examples:
// Good: Action events
'actionclicked'
'buttonclicked'
'itemselected'
// Good: State events
'loadstarted'
'loadcompleted'
'validationfailed'
// Good: Change events
'valuechanged'
'selectionchanged'
'filterchanged'
Event Handler Patterns
Naming:
- Prefix event handlers with
handle:handleClick,handleChange,handleSubmit - Be descriptive about what the handler does
Examples:
// Good: Descriptive handler names
handleClick() {
// Handle click
}
handleInputChange(event) {
const value = event.target.value;
this.value = value;
}
handleFormSubmit(event) {
event.preventDefault();
// Handle form submission
}
handleItemSelect(event) {
const selectedItem = event.detail.item;
this.selectedItem = selectedItem;
}
Event Best Practices
- Always include relevant data in event
detail - Use
bubbles: trueandcomposed: truefor standard events - Document event names and payload structure
- Handle events appropriately in parent components
- Use standard DOM events when possible
Error Handling and User Feedback
Error States
Usage: Track error states and display appropriate UI feedback.
Examples:
export default class MyComponent extends LightningElement {
error;
errorMessage;
hasError = false;
handleError(error) {
this.error = error;
this.hasError = true;
this.errorMessage = this.getErrorMessage(error);
}
getErrorMessage(error) {
if (error.body && error.body.message) {
return error.body.message;
}
return 'An unexpected error occurred. Please try again.';
}
clearError() {
this.error = undefined;
this.hasError = false;
this.errorMessage = undefined;
}
}
Template:
<template>
<!-- Good: Single element - lwc:if on the element itself -->
<div lwc:if={hasError} class="error-message">{errorMessage}</div>
<template lwc:else>
<!-- Normal content -->
</template>
</template>
Toast Notifications
Usage: Use lightning/platformShowToastEvent to show toast notifications for user feedback.
Examples:
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class MyComponent extends LightningElement {
showToast(title, message, variant) {
const event = new ShowToastEvent({
title: title,
message: message,
variant: variant || 'info'
});
this.dispatchEvent(event);
}
handleSuccess() {
this.showToast('Success', 'Operation completed successfully', 'success');
}
handleError(error) {
this.showToast('Error', 'An error occurred: ' + error.message, 'error');
}
}
Toast Variants:
success- Green toast for successful operationserror- Red toast for errorswarning- Yellow toast for warningsinfo- Blue toast for informational messages
Wire Service Error Handling
Usage: Handle errors from wire adapters appropriately.
Examples:
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
this.hasError = false;
} else if (error) {
this.error = error;
this.hasError = true;
this.handleWireError(error);
}
}
handleWireError(error) {
// Show toast notification
this.showToast('Error', 'Failed to load account data', 'error');
// Log error for debugging
console.error('Wire error:', error);
// Set error state for UI
this.errorMessage = this.getErrorMessage(error);
}
Error Boundary Pattern
Usage: Use errorCallback to catch and handle errors gracefully.
Examples:
export default class MyComponent extends LightningElement {
error;
hasError = false;
errorCallback(error, stack) {
// Log error
console.error('Component error:', error);
console.error('Stack trace:', stack);
// Set error state
this.error = error;
this.hasError = true;
// Show user-friendly error
this.showToast('Error', 'An error occurred. Please refresh the page.', 'error');
}
}
Error Handling Best Practices
- Always handle errors appropriately
- Provide user-friendly error messages
- Log errors for debugging
- Show toast notifications for user feedback
- Set error states for UI display
- Don't expose sensitive information in error messages
Computed Properties
Getters vs Methods
Usage: Use getters for computed properties that are used in templates. Use methods for computed values that require parameters or are used in JavaScript.
Getters:
// Good: Use getter for template-bound computed property
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
get isDisabled() {
return this.loading || !this.isValid;
}
get displayValue() {
return this.value || 'N/A';
}
Methods:
// Good: Use method when parameter is needed
formatValue(value) {
return value ? value.toUpperCase() : '';
}
calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
When to Use Getters
Use getters when:
- Property is used in the template
- Property is computed from other reactive properties
- Property doesn't require parameters
- Property should be reactive to changes in dependencies
Examples:
// Good: Getter used in template
get buttonClass() {
return `slds-button ${this.variant ? `slds-button_${this.variant}` : ''}`;
}
get isVisible() {
return this.items && this.items.length > 0;
}
get displayText() {
return this.text || this.defaultText || 'No text';
}
Getter Best Practices
- Keep getters simple and fast
- Avoid side effects in getters
- Don't modify component state in getters
- Cache expensive computations if needed
- Use getters for template-bound computed properties
Async Patterns
Async/Await
Usage: Use async/await for cleaner asynchronous code (preferred over Promises).
Examples:
// Good: Use async/await
async handleSubmit() {
try {
this.isLoading = true;
const result = await saveRecord(this.recordData);
this.showToast('Success', 'Record saved successfully', 'success');
this.isLoading = false;
} catch (error) {
this.handleError(error);
this.isLoading = false;
}
}
// Good: Multiple async operations
async loadAllData() {
try {
const [accounts, contacts] = await Promise.all([
getAccounts(),
getContacts()
]);
this.accounts = accounts;
this.contacts = contacts;
} catch (error) {
this.handleError(error);
}
}
// Good: Basic async operation
async loadData() {
try {
this.isLoading = true;
const data = await fetchData();
this.data = data;
this.isLoading = false;
} catch (error) {
this.handleError(error);
this.isLoading = false;
}
}
Promises
Usage: Use Promises for asynchronous operations when async/await is not available or appropriate.
Note: async/await is preferred over Promises for better readability and error handling. Use Promises only when necessary.
Examples:
// Acceptable: Handle promise with then/catch (async/await preferred)
loadData() {
return fetchData()
.then((data) => {
this.data = data;
this.isLoading = false;
})
.catch((error) => {
this.handleError(error);
this.isLoading = false;
});
}
Navigation with Async
Usage: Handle navigation promises appropriately.
Examples:
async handleNavigate() {
try {
await this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
} catch (error) {
this.showToast('Error', 'Failed to navigate', 'error');
}
}
async handleGenerateUrl() {
try {
const url = await this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
window.open(url, '_blank');
} catch (error) {
this.showToast('Error', 'Failed to generate URL', 'error');
}
}
Async Best Practices
- Use
async/awaitinstead of Promises when possible - Always handle errors with try/catch
- Set loading states appropriately
- Use
Promise.allfor parallel operations - Clean up async operations in
disconnectedCallbackif needed
Accessibility (Section 508 Compliance)
ARIA Labels
Usage: Always provide ARIA labels for interactive elements and components.
Guidelines:
- Use
aria-labelfor elements without visible text - Use
aria-labelledbyto reference visible labels - Use
aria-describedbyfor additional descriptions - Use
aria-livefor dynamic content updates - Lightning components (like
lightning-input,lightning-button, etc.) automatically generate proper label associations when you provide alabelattribute - noaria-labelneeded in these cases - For
lightning-inputwith hidden labels, uselabel-hiddenattribute along witharia-label
Examples:
<!-- Good: Button with aria-label -->
<button aria-label="Close dialog" onclick={handleClose}>
<lightning-icon icon-name="utility:close"></lightning-icon>
</button>
<!-- Good: lightning-input with label (aria-label not needed - label is automatically linked) -->
<lightning-input
label="Account Name"
value={accountName}
required>
</lightning-input>
<!-- Good: lightning-input without visible label (use label-hidden + aria-label) -->
<lightning-input
label-hidden
aria-label="Account Name"
value={accountName}
required>
</lightning-input>
<!-- Good: Custom input element (aria-label needed - no automatic label) -->
<input
type="text"
aria-label="Account Name"
value={accountName}>
</input>
<!-- Good: Custom element with aria attributes -->
<div
role="button"
tabindex="0"
aria-label="Select item"
aria-pressed={isSelected}
onkeydown={handleKeyDown}
onclick={handleClick}>
{itemName}
</div>
Keyboard Navigation
Usage: Ensure all interactive elements are keyboard accessible.
Guidelines:
- Use semantic HTML elements (
button,a,input) when possible - Add
tabindex="0"for custom interactive elements - Handle keyboard events (
keydown,keypress,keyup) - Support standard keyboard shortcuts (Enter, Space, Escape, Arrow keys)
Examples:
// Good: Handle keyboard events
handleKeyDown(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleClick();
} else if (event.key === 'Escape') {
this.handleClose();
}
}
// Good: Handle arrow key navigation
handleKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
this.selectNextItem();
break;
case 'ArrowUp':
event.preventDefault();
this.selectPreviousItem();
break;
case 'Enter':
event.preventDefault();
this.selectCurrentItem();
break;
default:
break;
}
}
Template:
<!-- Good: Keyboard accessible custom element -->
<div
role="button"
tabindex="0"
aria-label="Click to expand"
onkeydown={handleKeyDown}
onclick={handleClick}>
Content
</div>
Semantic HTML
Usage: Use semantic HTML elements to convey meaning to screen readers.
Guidelines:
- Use
<button>for buttons, not<div>with click handlers - Use
<a>for links, not<div>with navigation - Use proper heading hierarchy (
h1,h2,h3, etc.) - Use
<label>for form inputs - Use ARIA roles when semantic HTML isn't sufficient
Examples:
<!-- Good: Use semantic button -->
<button onclick={handleClick} aria-label="Submit form">
Submit
</button>
<!-- Bad: Div masquerading as button -->
<div onclick={handleClick} role="button" tabindex="0">
Submit
</div>
<!-- Good: Use semantic link -->
<a href="#" onclick={handleNavigate}>Go to Account</a>
<!-- Good: Proper heading hierarchy -->
<h1>Page Title</h1>
<h2>Section Title</h2>
<h3>Subsection Title</h3>
Screen Reader Support
Usage: Ensure components are accessible to screen readers.
Guidelines:
- Provide text alternatives for images and icons
- Use
alttext for images - Use
aria-labeloraria-labelledbyfor icons - Announce dynamic content changes with
aria-live - Use
roleattributes appropriately
Examples:
<!-- Good: Icon with aria-label -->
<lightning-icon
icon-name="utility:info"
alternative-text="Information"
title="Information">
</lightning-icon>
<!-- Good: Dynamic content with aria-live -->
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
<!-- Good: Image with alt text -->
<img src={imageUrl} alt="Account logo" />
Focus Management
Usage: Manage focus appropriately for accessibility.
Guidelines:
- Set focus on important elements when appropriate
- Return focus to trigger element after modal/dialog closes
- Provide visible focus indicators
- Don't trap focus unnecessarily
Examples:
// Good: Set focus on element
@api focus() {
const input = this.template.querySelector('input');
if (input) {
input.focus();
}
}
// Good: Return focus after action
handleClose() {
// Save trigger element reference
const triggerElement = document.activeElement;
// Close modal
this.isOpen = false;
// Return focus
this.returnFocus(triggerElement);
}
Accessibility Best Practices
- Always provide ARIA labels for interactive elements
- Ensure keyboard navigation works for all functionality
- Use semantic HTML elements when possible
- Test with screen readers
- Provide text alternatives for images and icons
- Manage focus appropriately
- Support standard keyboard shortcuts
- Ensure sufficient color contrast (WCAG AA minimum)
Component Metadata
.js-meta.xml Structure
Usage: Define component metadata in .js-meta.xml file.
Required Elements:
apiVersion- API version for the componentisExposed- Whether component is exposed for usemasterLabel- Display label for the componentdescription- Description of the componenttargets- Where the component can be usedtargetConfigs- Configuration for specific targets
Example:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Action Button</masterLabel>
<description>Reusable action button component with multiple variants and customization options.</description>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
<target>lightning__FlowScreen</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__AppPage,lightning__RecordPage,lightning__HomePage">
<property name="label" type="String" label="Button Label" description="The text displayed on the button." />
<property name="variant" type="String" label="Variant" datasource="brand,neutral,destructive" description="The button variant style." />
<property name="disabled" type="Boolean" label="Disabled" description="Whether the button is disabled." default="false" />
</targetConfig>
<targetConfig targets="lightningCommunity__Default">
<property name="label" type="String" label="Button Label" description="The text displayed on the button." />
<property name="variant" type="String" label="Variant" datasource="brand,neutral,destructive" description="The button variant style." />
</targetConfig>
<targetConfig targets="lightning__FlowScreen">
<property name="label" type="String" label="Button Label" role="inputOnly" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
API Version
Usage: Use the latest API version from the project (currently 65.0).
Guidelines:
- Keep API versions consistent across components
- Update API versions as part of regular maintenance
- Don't manually set API version unless necessary
Targets
Common Targets:
lightning__AppPage- Lightning App pageslightning__RecordPage- Record detail pageslightning__HomePage- Home pageslightningCommunity__Page- Experience Cloud pageslightningCommunity__Default- Experience Cloud default pageslightning__FlowScreen- Flow screens
TargetConfigs
Usage: Define different property configurations for different targets.
Guidelines:
- Use
targetConfigsto customize properties per target - Use
datasourcefor property values with predefined options - Use
role="inputOnly"for Flow input-only properties - Provide meaningful
labelanddescriptionfor each property
Property Types:
String- Text valuesBoolean- True/false valuesInteger- Numeric valuesObject- Complex objects
Component Metadata Best Practices
- Always provide meaningful
masterLabelanddescription - Define appropriate
targetsfor component usage - Use
targetConfigsto customize properties per target - Provide helpful
descriptiontext for each property - Use
datasourcefor properties with predefined options - Keep API versions consistent
Import Organization
Import Order
Guidelines:
- Lightning Web Component imports (
lwc) - Lightning platform imports (
lightning/*) - Salesforce imports (
@salesforce/*) - Third-party library imports
- Local utility imports (
c/*)
Examples:
// 1. LWC imports
import { LightningElement, api, wire } from 'lwc';
// 2. Lightning platform imports
import { NavigationMixin } from 'lightning/navigation';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord } from 'lightning/uiRecordApi';
import { MessageContext } from 'lightning/messageService';
// 3. Salesforce imports
import getAccountData from '@salesforce/apex/AccountController.getAccountData';
import SELECTED_CHANNEL from '@salesforce/messageChannel/sfcAction__c';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
// 4. Third-party imports (if any)
// import { Chart } from 'chart.js';
// 5. Local utility imports
import { isEmpty, showToast } from 'c/sfcUtils';
import SfcRecordUtils from 'c/sfcRecordUtils';
Import Grouping
Guidelines:
- Group imports by category
- Use blank lines between import groups
- Sort imports alphabetically within each group (optional but recommended)
Examples:
// LWC core
import { LightningElement, api, wire } from 'lwc';
// Lightning platform
import { NavigationMixin } from 'lightning/navigation';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
// Salesforce
import getAccountData from '@salesforce/apex/AccountController.getAccountData';
import SELECTED_CHANNEL from '@salesforce/messageChannel/sfcAction__c';
// Local utilities
import { isEmpty } from 'c/sfcUtils';
Import Best Practices
- Organize imports by category
- Use consistent import ordering
- Remove unused imports
- Use named imports when possible
- Group related imports together
Documentation
JSDoc Requirements
Usage: All public methods and complex private methods must have JSDoc comments.
Class Documentation:
/**
* @description Reusable action button component with multiple variants and customization options.
*
* This component provides a flexible button implementation that supports various
* styles, icons, and actions. It can be used as a standard button or as a link,
* and supports navigation, message channel publishing, and custom click handlers.
*
* Key features:
* - Multiple button variants (brand, neutral, destructive, etc.)
* - Icon support (left, right, or both)
* - Navigation support via NavigationMixin
* - Message channel publishing
* - Custom styling via CSS custom properties
* - Full accessibility support
*
* @example
* <c-sfc-action
* label="Click Me"
* variant="brand"
* icon-name="utility:add"
* onactionclicked={handleClick}>
* </c-sfc-action>
*
* @author callinj
* @since 01-01-2024 Initial Implementation - callinj
*
* @see NavigationMixin For navigation functionality
* @see MessageContext For message channel support
*/
export default class SfcAction extends NavigationMixin(LightningElement) {
}
Method Documentation:
/**
* Sets focus on the button element.
* @returns {void}
*/
@api focus() {
const button = this.template.querySelector('button');
if (button) {
button.focus();
}
}
/**
* Handles the click event and dispatches custom event.
* @param {Event} event - The click event object
* @returns {void}
*/
handleClick(event) {
this.dispatchEvent(new CustomEvent('actionclicked', {
detail: {
recordId: this.recordId,
action: 'clicked'
},
bubbles: true,
composed: true
}));
}
/**
* Validates the component state and returns validation result.
* @returns {Object} Validation result with isValid flag and error message
* @returns {boolean} returns.isValid - Whether the component is valid
* @returns {string} returns.error - Error message if invalid
*/
@api validate() {
if (!this.value) {
return {
isValid: false,
error: 'Value is required'
};
}
return {
isValid: true,
error: undefined
};
}
Required Documentation Elements
For Classes:
@description- Brief description followed by expanded details- Key features - Bulleted list of main features/capabilities
- Additional context - Explanation of how the component works and interacts with other components
@example- Usage example in code block@author- Comma-separated list of contributors@since- Date and initial implementation information@see- References to related components/utilities
For Methods:
- Description - Plain text description of what the method does
@param- Parameter descriptions with types@returns- Return value description with type@example- Usage example when helpful
Inline Comments Guidelines
- Prefer self-documenting code - Use clear variable and method names
- Add comments when logic is complex - Explain "why" not "what"
- Avoid obvious comments - Don't restate what the code clearly shows
- Update comments with code changes - Keep comments in sync with code
Good Example:
// Use safe navigation to avoid errors when record type is null
const objectName = record?.getSObjectType()?.getDescribe()?.getName();
Bad Example:
// Set the label
this.label = 'Click Me';
CSS Standards
SASS/SCSS Usage
Usage: All component styles should be written in SASS/SCSS (source files) and placed in the __styles__ folder within the component directory. SASS/SCSS files compile to CSS files that are stored in the main component directory.
Guidelines:
- Use SCSS syntax (preferred) or SASS syntax for source files
- Place all SASS/SCSS source files in the
__styles__folder - Main style file should be named
componentName.scss - Use SASS features (variables, mixins, nesting, etc.) appropriately
- Organize related styles into partial files (
_variables.scss,_mixins.scss, etc.) - SASS/SCSS files compile to CSS files during the build process
- Compiled CSS files (
componentName.css) are stored in the main component directory (same level as.js,.html,.js-meta.xml) - The compiled CSS file is what LWC uses at runtime
- Edit SASS/SCSS source files, not the compiled CSS files
File Structure:
src/components/lwc/sfcText/
├── sfcText.js
├── sfcText.html
├── sfcText.css (compiled from __styles__/sfcText.scss)
├── sfcText.js-meta.xml
└── __styles__
├── sfcText.scss (SASS source - edit this)
├── _variables.scss (SASS partial)
└── _mixins.scss (SASS partial)
Compilation Process:
- Developer edits SASS/SCSS files in
__styles__/folder - Build process compiles SASS/SCSS to CSS
- Compiled CSS file (
componentName.css) is generated in main component directory - LWC uses the compiled CSS file at runtime
Main Stylesheet Example:
// sfcText.scss
@import 'variables';
@import 'mixins';
:host {
--sfc-text-font-size: 1rem;
--sfc-text-font-weight: 700;
--sfc-text-line-height: 1.5;
}
:host h1 {
font-size: var(--sfc-text-font-size, 2rem);
font-weight: var(--sfc-text-font-weight, 700);
}
CSS Custom Properties
Usage: Use CSS custom properties (CSS variables) for theming and dynamic styling.
Guidelines:
- Use component prefix for custom properties:
--sfc-component-name - Define custom properties in
:hostselector - Provide default values
- Use descriptive names
- Can be defined in SASS variables and converted to CSS custom properties
Examples:
// Using SASS variables that become CSS custom properties
:host {
--sfc-action-color-background: #{$action-color-primary};
--sfc-action-color-border: #{$action-color-primary};
--sfc-action-color-background-hover: #{$action-color-primary-hover};
--sfc-text-font-size: 1rem;
--sfc-text-font-weight: 700;
--sfc-text-line-height: 1.5;
}
Or using pure CSS custom properties:
:host {
--sfc-action-color-background: #0070d2;
--sfc-action-color-border: #0070d2;
--sfc-action-color-background-hover: #005fb2;
--sfc-text-font-size: 1rem;
--sfc-text-font-weight: 700;
--sfc-text-line-height: 1.5;
}
JavaScript Usage:
setColors() {
const component = this.refs.sfcComponent;
if (component) {
component.style.setProperty('--sfc-action-color-background', this.customColor);
}
}
SLDS Usage
Usage: Use Salesforce Lightning components whenever possible. SLDS classes should be used as a fallback when Lightning components don't exist or don't meet requirements.
Priority Order:
- Use Lightning components first -
lightning-button,lightning-layout,lightning-input, etc. - Use SLDS classes as fallback - When Lightning components don't exist or don't meet specific requirements
- Use custom CSS last - Only when neither Lightning components nor SLDS classes meet requirements
Guidelines:
- Prefer Lightning components over building custom components with SLDS classes
- Use SLDS utility classes for spacing, alignment, etc. when Lightning components don't provide the needed functionality
- Only create custom CSS when Lightning components and SLDS classes don't meet requirements
- Follow SLDS naming conventions when using SLDS classes directly
Examples:
<!-- Good: Use Lightning components (preferred) -->
<lightning-button variant="brand" label="Click Me"></lightning-button>
<lightning-layout>
<lightning-layout-item size="12" medium-size="6" large-size="4">
Content
</lightning-layout-item>
</lightning-layout>
<lightning-input label="Account Name" value={accountName}></lightning-input>
<!-- Acceptable: Use SLDS classes when Lightning components don't meet requirements -->
<button class="slds-button slds-button_brand">
Click Me
</button>
<div class="slds-text-align_center slds-m-vertical_medium">
Content
</div>
<!-- Bad: Building custom button when lightning-button exists -->
<button class="slds-button slds-button_brand" onclick={handleClick}>
Click Me
</button>
<!-- Should use: <lightning-button variant="brand" label="Click Me" onclick={handleClick}></lightning-button> -->
<!-- Bad: Using slds-grid when lightning-layout exists -->
<div class="slds-grid slds-gutters">
<div class="slds-col">Content</div>
</div>
<!-- Should use: <lightning-layout> with <lightning-layout-item> -->
Scoped Styles
Usage: All SASS/SCSS is automatically scoped to the component. Use :host selector for component-level styles.
Guidelines:
- Use
:hostfor component-level styles - Use element selectors for child elements
- Avoid global styles (use
:hostinstead) - Use
:host(.class)for conditional component styles - Take advantage of SASS nesting for better organization
Examples:
// Good: Component-level styles
:host {
display: block;
padding: 1rem;
// Good: Conditional component styles using nesting
&.custom-class {
background-color: #f3f3f3;
}
// Good: Child element styles using nesting
h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
// Good: SLDS integration using nesting
.slds-button {
margin-top: 1rem;
}
}
Alternative (without nesting):
// Good: Component-level styles
:host {
display: block;
padding: 1rem;
}
// Good: Conditional component styles
:host(.custom-class) {
background-color: #f3f3f3;
}
// Good: Child element styles
:host h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
// Good: SLDS integration
:host .slds-button {
margin-top: 1rem;
}
SASS/SCSS Organization
Guidelines:
- Organize SASS/SCSS by component sections
- Group related styles together
- Use SASS partials (
_filename.scss) for reusable code (variables, mixins) - Use
@importto include partials in main stylesheet - Use comments to separate sections
- Follow consistent formatting
- Use nesting appropriately (max 3-4 levels deep)
Example Structure:
// Main stylesheet: sfcText.scss
@import 'variables';
@import 'mixins';
// Component host styles
:host {
display: block;
// Typography
h1 {
font-size: 2rem;
}
p {
font-size: 1rem;
}
// Layout
.container {
display: flex;
flex-direction: column;
}
// Interactive elements
button {
padding: 0.5rem 1rem;
&:hover {
background-color: #f3f3f3;
}
}
// Utility classes
.hidden {
display: none;
}
}
Partial Files:
// _variables.scss
$color-primary: #0070d2;
$color-secondary: #16325c;
$spacing-small: 0.5rem;
$spacing-medium: 1rem;
// _mixins.scss
@mixin button-variant($color) {
background-color: $color;
border-color: $color;
&:hover {
background-color: darken($color, 10%);
}
}
SASS/SCSS Best Practices
- Use Lightning components first, then SLDS classes, then custom CSS
- Use CSS custom properties for theming
- Keep SASS/SCSS scoped to component
- Use
:hostfor component-level styles - Organize SASS/SCSS logically with partials
- Use SASS variables and mixins for reusability
- Use meaningful class names
- Avoid deep nesting (max 3-4 levels)
- Use comments to organize sections
- Place all style files in
__styles__folder - Use SCSS syntax (preferred) or SASS syntax consistently
Code Style and Formatting
Prettier Compliance
Usage: Use Prettier for code formatting (configured in project).
Guidelines:
- Run Prettier before committing code
- Ensure consistent formatting across the codebase
- Don't disable Prettier unless absolutely necessary
Indentation
Usage: Use 4 spaces for indentation (Prettier default).
Line Length
Guidelines:
- Keep lines under 100 characters when possible
- Break long lines appropriately
- Use proper indentation for continuation
Examples:
// Good: Break long lines appropriately
const result = await this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
actionName: 'view'
}
});
// Good: Break method chains
const accounts = await SFC_QueryBuilderService
.selectFrom('Account')
.withFields('Id', 'Name')
.execute();
Code Formatting Best Practices
- Use Prettier for consistent formatting
- Keep lines readable (under 100 characters)
- Use proper indentation
- Format consistently across the codebase
Code Complexity
Cognitive Complexity Limits
Guidelines:
- Keep methods simple and focused
- Refactor complex methods into smaller methods
- Use early returns to reduce nesting
- Avoid deep nesting (max 4 levels)
Examples:
// Bad: Too complex
handleClick() {
if (this.url) {
if (this.copyUrl) {
this.handleCopy();
} else {
if (this.newTab) {
this[NavigationMixin.GenerateUrl]({
type: 'standard__webPage',
attributes: { url: this.url }
}).then((url) => {
window.open(url, '_blank');
});
} else {
if (REGEX_SALESFORCE.test(this.url)) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.url,
actionName: 'view'
}
});
} else {
this[NavigationMixin.Navigate]({
type: 'standard__webPage',
attributes: { url: this.url }
});
}
}
}
} else {
if (this.printPage) {
window.print();
}
}
}
// Good: Refactored with early returns and helper methods
handleClick() {
if (this.url) {
this.handleUrlClick();
} else if (this.printPage) {
window.print();
}
this.dispatchEvent(new CustomEvent('actionclicked'));
}
handleUrlClick() {
if (this.copyUrl) {
this.handleCopy();
return;
}
if (this.newTab) {
this.openInNewTab();
} else {
this.navigateToUrl();
}
}
navigateToUrl() {
const navigateConfig = REGEX_SALESFORCE.test(this.url)
? this.getRecordPageConfig()
: this.getWebPageConfig();
this[NavigationMixin.Navigate](navigateConfig);
}
Method Length Guidelines
Guidelines:
- Keep methods under 50 lines when possible
- Refactor long methods into smaller, focused methods
- Each method should do one thing well
Code Complexity Best Practices
- Keep methods simple and focused
- Use early returns to reduce nesting
- Refactor complex logic into helper methods
- Avoid deep nesting
- Use descriptive method names
Performance Optimization
Lazy Loading
Usage: Use lazy loading for heavy components or data that's not immediately needed.
Guidelines:
- Load data on demand when possible
- Use
lwc:ifto conditionally render heavy components - Load third-party libraries only when needed
Examples:
// Good: Lazy load data
async loadData() {
if (!this.dataLoaded) {
this.isLoading = true;
this.data = await fetchData();
this.dataLoaded = true;
this.isLoading = false;
}
}
// Good: Conditionally render heavy component
get shouldRenderChart() {
return this.data && this.data.length > 0;
}
<!-- Good: Single element - lwc:if on the element itself -->
<c-chart-component lwc:if={shouldRenderChart} data={data}></c-chart-component>
Wire Adapter Optimization
Usage: Optimize wire adapter usage for performance.
Guidelines:
- Use
cacheable=truefor Apex methods when possible - Use reactive parameters efficiently
- Avoid unnecessary wire adapter calls
- Handle wire adapter errors appropriately
Examples:
// Good: Use cacheable for read-only data
@wire(getAccountData, { accountId: '$recordId' })
wiredAccountData({ error, data }) {
// Handle data/error
}
// Apex method with cacheable
@AuraEnabled(cacheable=true)
public static Account getAccountData(Id accountId) {
return [SELECT Id, Name FROM Account WHERE Id = :accountId];
}
Rendering Optimization
Usage: Optimize component rendering for performance.
Guidelines:
- Use
lwc:ifto conditionally render elements - Avoid unnecessary re-renders
- Use
lwc:keywithlwc:forfor efficient list rendering - Minimize DOM manipulation in
renderedCallback
Examples:
<!-- Good: Single element - lwc:if on the element itself -->
<div lwc:if={isVisible}>Content</div>
<!-- Good: Multiple elements - wrap in template -->
<template lwc:if={isVisible}>
<div>Content</div>
<p>Additional content</p>
</template>
<!-- Good: Use lwc:key for efficient list rendering -->
<template lwc:for={items} lwc:key={item.id}>
<div>{item.name}</div>
</template>
Performance Best Practices
- Use lazy loading for heavy components/data
- Optimize wire adapter usage
- Minimize DOM manipulation
- Use conditional rendering appropriately
- Use
lwc:keywith lists - Avoid unnecessary re-renders
- Cache expensive computations
Component Composition
Parent-Child Communication
Usage: Use events for parent-child communication.
Child Component:
// Child dispatches event
handleClick() {
this.dispatchEvent(new CustomEvent('itemclicked', {
detail: {
itemId: this.itemId,
itemData: this.itemData
},
bubbles: true,
composed: true
}));
}
Parent Component:
<!-- Parent listens to event -->
<c-child-component onitemclicked={handleItemClicked}></c-child-component>
// Parent handles event
handleItemClicked(event) {
const { itemId, itemData } = event.detail;
// Handle event
}
Public API Pattern
Usage: Expose public methods for parent components to control child behavior.
Examples:
// Child component exposes public methods
@api focus() {
const input = this.template.querySelector('input');
if (input) {
input.focus();
}
}
@api reset() {
this.value = '';
this.error = undefined;
}
// Parent component calls public methods
handleFocusChild() {
const child = this.template.querySelector('c-child-component');
if (child) {
child.focus();
}
}
Component Interaction Patterns
Guidelines:
- Use events for child-to-parent communication
- Use public methods for parent-to-child control
- Use message channels for cross-component communication
- Keep component interfaces clear and documented
Composition Best Practices
- Use events for upward communication
- Use public methods for downward control
- Use message channels for sibling communication
- Keep components loosely coupled
- Document component interfaces
- Use clear, descriptive event names
Testing Standards
Note: This section provides LWC-specific testing guidelines. For comprehensive testing standards covering all aspects of test coverage, test structure, and best practices, see the Testing Standards document.
Jest Testing Patterns
Usage: Use Jest with @salesforce/sfdx-lwc-jest for LWC testing.
Guidelines:
- Write tests for all public methods and event handlers
- Follow 1:1 test method to code method relationship when possible
- Test user interactions and events
- Mock wire adapters and Apex methods
- Test error scenarios
Example Test Structure:
import { createElement } from 'lwc';
import SfcAction from 'c/sfcAction';
import { NavigationMixin } from 'lightning/navigation';
describe('c-sfc-action', () => {
afterEach(() => {
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('renders button with label', () => {
const element = createElement('c-sfc-action', {
is: SfcAction
});
element.label = 'Click Me';
document.body.appendChild(element);
return Promise.resolve().then(() => {
const button = element.shadowRoot.querySelector('button');
expect(button).not.toBeNull();
expect(button.textContent).toBe('Click Me');
});
});
it('dispatches actionclicked event on click', () => {
const element = createElement('c-sfc-action', {
is: SfcAction
});
document.body.appendChild(element);
const handler = jest.fn();
element.addEventListener('actionclicked', handler);
return Promise.resolve().then(() => {
const button = element.shadowRoot.querySelector('button');
button.click();
expect(handler).toHaveBeenCalled();
});
});
it('calls focus method successfully', () => {
const element = createElement('c-sfc-action', {
is: SfcAction
});
document.body.appendChild(element);
return Promise.resolve().then(() => {
const button = element.shadowRoot.querySelector('button');
const focusSpy = jest.spyOn(button, 'focus');
element.focus();
expect(focusSpy).toHaveBeenCalled();
});
});
});
Mocking Wire Adapters
Usage: Mock wire adapters in tests.
Examples:
import { createElement } from 'lwc';
import AccountDetail from 'c/accountDetail';
import { getRecord } from 'lightning/uiRecordApi';
// Mock the wire adapter
jest.mock(
'lightning/uiRecordApi',
() => ({
getRecord: jest.fn()
}),
{ virtual: true }
);
describe('c-account-detail', () => {
it('displays account data when wire adapter returns data', () => {
const mockRecord = {
fields: {
Name: { value: 'Test Account' },
Industry: { value: 'Technology' }
}
};
getRecord.mockResolvedValue(mockRecord);
const element = createElement('c-account-detail', {
is: AccountDetail
});
element.recordId = '001xx000003DGbQAAW';
document.body.appendChild(element);
return Promise.resolve().then(() => {
const nameElement = element.shadowRoot.querySelector('.account-name');
expect(nameElement.textContent).toBe('Test Account');
});
});
});
Testing Best Practices
- Write tests for all public methods
- Test event dispatching and handling
- Mock wire adapters and Apex methods
- Test error scenarios
- Test accessibility features
- Follow 1:1 test method to code method relationship when possible
- Use descriptive test names
- Clean up after each test
Security
XSS Prevention
Usage: Prevent Cross-Site Scripting (XSS) attacks by properly handling user input.
Guidelines:
- Never use
innerHTMLwith user input - Use LWC's built-in data binding (safe by default)
- Sanitize user input if needed
- Use
lightning-formatted-*components for safe rendering
Examples:
<!-- Good: Safe data binding -->
<div>{userInput}</div>
<!-- Good: Use lightning-formatted components -->
<lightning-formatted-rich-text value={richText}></lightning-formatted-rich-text>
<!-- Bad: Unsafe innerHTML -->
<div lwc:dom="manual"></div>
// Bad: Unsafe innerHTML manipulation
renderedCallback() {
const div = this.template.querySelector('.content');
div.innerHTML = this.userInput; // UNSAFE!
}
// Good: Use safe data binding
// Template: <div>{userInput}</div>
Input Validation
Usage: Validate all user input before processing.
Guidelines:
- Validate input on both client and server side
- Provide clear error messages for invalid input
- Sanitize input when necessary
- Use type checking and validation utilities
Examples:
// Good: Validate input
handleInputChange(event) {
const value = event.target.value;
if (!this.isValidInput(value)) {
this.error = 'Invalid input';
return;
}
this.value = value;
this.error = undefined;
}
isValidInput(value) {
// Validation logic
return value && value.length > 0 && value.length <= 255;
}
Secure Data Handling
Guidelines:
- Don't expose sensitive data in component properties
- Use secure methods for data transmission
- Handle errors without exposing sensitive information
- Follow Salesforce security best practices
Security Best Practices
- Prevent XSS attacks
- Validate all user input
- Handle data securely
- Don't expose sensitive information
- Use LWC's safe data binding
- Follow Salesforce security guidelines
Browser Compatibility
Supported Browsers
Salesforce LWC supports:
- Chrome (latest 2 versions)
- Firefox (latest 2 versions)
- Safari (latest 2 versions)
- Edge (latest 2 versions)
Guidelines:
- Test components in supported browsers
- Use feature detection when needed
- Avoid browser-specific code when possible
- Use polyfills for unsupported features if necessary
Compatibility Considerations
Guidelines:
- Use standard JavaScript features
- Avoid experimental features
- Test in all supported browsers
- Use feature detection for optional features
Examples:
// Good: Feature detection
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(this.url);
} else {
// Fallback for older browsers
this.fallbackCopyToClipboard(this.url);
}
API Version Standards
API Version Selection
Usage: Use the latest API version from the project (currently 65.0).
Guidelines:
- Keep API versions consistent across components
- Update API versions as part of regular maintenance
- Don't manually set API version unless necessary
- Document any exceptions to standard API version
API Version Maintenance
Guidelines:
- Review API versions periodically
- Update to latest API version during maintenance windows
- Test components after API version updates
- Document API version changes
API Version Best Practices
- Use consistent API versions
- Update API versions regularly
- Test after API version updates
- Document API version exceptions
Code Maintenance
TODO/FIXME Comments
Usage: TODO and FIXME comments are acceptable if there is a discussion, issue, or bug created to capture that work.
Format:
// TODO: [Issue #123] Migrate to lwc:if directive
// FIXME: [Bug #456] Handle edge case when recordId is null
Bad Example:
// TODO: Fix this later
// FIXME: This doesn't work
Conditional Rendering Migration
Requirement: Any existing if:true/if:false should be migrated to lwc:if when touching the component.
Migration Guidelines:
- Single element: Place
lwc:ifdirectly on the element (no template wrapper needed) - Multiple elements: Wrap in
<template>tag withlwc:if - No logic in brackets: Convert expressions to getters
Examples:
<!-- Old syntax (should be migrated) -->
<div if:true={isVisible}>Content</div>
<lightning-button if:true={showButton} label="Click Me"></lightning-button>
<!-- New syntax (preferred) - single element -->
<div lwc:if={isVisible}>Content</div>
<lightning-button lwc:if={showButton} label="Click Me"></lightning-button>
<!-- New syntax (preferred) - multiple elements -->
<template lwc:if={isVisible}>
<div>Content</div>
<p>Additional content</p>
</template>
<!-- Old syntax with logic (should be migrated) -->
<div if:true={status === 'success'}>Success</div>
<!-- New syntax - use getter instead of logic in brackets -->
<!-- JavaScript: get isSuccess() { return this.status === 'success'; } -->
<div lwc:if={isSuccess}>Success</div>
Deprecation Process
Note: This topic requires further discussion and planning. The deprecation process should include:
- Documentation - Document why the code is deprecated and what to use instead
- Transition Period - Provide a grace period for migration
- Process to List Usages/Updates - Track all usages and required updates
Placeholder for Future Implementation:
/**
* @description [DEPRECATED] Use lwc:if directive instead
* @deprecated This property will be removed in version 2.0. Use lwc:if in template instead.
* @see Component documentation for migration guide
*/
@api get isVisible() {
return this._isVisible;
}
Code Maintenance Best Practices
- Address TODOs when touching components
- Migrate
if:true/if:falsetolwc:ifwhen updating components - Keep code up to date with latest LWC features
- Remove unused code
- Update documentation with code changes
- Follow deprecation process when removing features
Change History
Version 1.0 - 2026-01-14
- Initial release of LWC coding standards documentation
Last Updated: 2026-01-14
Version: 1.0