Author: callinj
Name: Joe Callin
Title: Sr. Salesforce Developer
Email: joe@jcallin.dev

Table of Contents

  1. Introduction
  2. Naming Conventions
  3. Component Structure and Organization
  4. Property Decorators
  5. Public Methods
  6. Template Directives
  7. Slot Usage
  8. Wire Adapters
  9. Lightning Data Service
  10. Message Channels
  11. Navigation Patterns
  12. Lifecycle Hooks
  13. Event Handling
  14. Error Handling and User Feedback
  15. Computed Properties
  16. Async Patterns
  17. Accessibility (Section 508 Compliance)
  18. Component Metadata
  19. Import Organization
  20. Documentation
  21. CSS Standards
  22. Code Style and Formatting
  23. Code Complexity
  24. Performance Optimization
  25. Component Composition
  26. Testing Standards
  27. Security
  28. Browser Compatibility
  29. API Version Standards
  30. 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.

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 @api decorator

  • 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., accountName not name, buttonElement not element)
  • 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 class
  • componentName.html - Component template
  • componentName.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.scss or componentName.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:

  1. Imports - All import statements
  2. Constants - Constants defined outside the class
  3. Class Declaration - Class definition extending LightningElement
  4. Private Properties - Private backing properties (prefixed with _) - ONLY used within this JS file, never in templates
  5. Private Reactive Properties - Private reactive properties (can be used in templates)
  6. Computed Properties (Getters) - Getter methods
  7. Public Properties - @api properties
  8. Public Methods - @api methods
  9. Wire Adapters - @wire properties
  10. Lifecycle Hooks - connectedCallback, renderedCallback, disconnectedCallback, errorCallback
  11. Event Handlers - Event handler methods
  12. 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:

  1. 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
  2. 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
  3. 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 @api for properties that need to be configurable from outside the component
  • Use @api for methods that need to be callable from parent components
  • Properties decorated with @api are 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 @track only 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 @wire for 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 @track only when you need to track deep object/array changes
  • Most use cases don't require @track in modern LWC

Public Methods

When to Expose Public Methods

Expose @api methods when:

  1. Parent components need to control child behavior - Focus, click, reset, etc.
  2. Component needs to be controlled externally - Show/hide, enable/disable programmatically
  3. 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:

  1. Async operations - Promises, setTimeout, setInterval callbacks that might complete after disconnection
  2. 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() or handle()

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:if directly on that element
  • Multiple elements: When controlling multiple elements, wrap them in a <template> tag with lwc: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:key with lwc:for for 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:ref instead of querySelector when possible for better performance
  • Access refs in renderedCallback or 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:if directly 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=true for 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 disconnectedCallback to prevent memory leaks
  • Check if messageContext exists before publishing/subscribing
  • Use meaningful message payloads with clear structure
  • Document message channel usage in component documentation

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');
        });
    }
}

Common Navigation Types:

  • standard__recordPage - Navigate to a record detail page
  • standard__objectPage - Navigate to an object's home/list view
  • standard__webPage - Navigate to an external URL
  • standard__namedPage - Navigate to a named page
  • standard__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'
    }
});
  • Use NavigationMixin for all navigation within Salesforce
  • Use GenerateUrl when 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.querySelector or lwc: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 detail property
  • Use bubbles: true and composable: true for 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: true and composed: true for 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 operations
  • error - Red toast for errors
  • warning - Yellow toast for warnings
  • info - 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;
        });
}

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/await instead of Promises when possible
  • Always handle errors with try/catch
  • Set loading states appropriately
  • Use Promise.all for parallel operations
  • Clean up async operations in disconnectedCallback if needed

Accessibility (Section 508 Compliance)

ARIA Labels

Usage: Always provide ARIA labels for interactive elements and components.

Guidelines:

  • Use aria-label for elements without visible text
  • Use aria-labelledby to reference visible labels
  • Use aria-describedby for additional descriptions
  • Use aria-live for dynamic content updates
  • Lightning components (like lightning-input, lightning-button, etc.) automatically generate proper label associations when you provide a label attribute - no aria-label needed in these cases
  • For lightning-input with hidden labels, use label-hidden attribute along with aria-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 alt text for images
  • Use aria-label or aria-labelledby for icons
  • Announce dynamic content changes with aria-live
  • Use role attributes 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 component
  • isExposed - Whether component is exposed for use
  • masterLabel - Display label for the component
  • description - Description of the component
  • targets - Where the component can be used
  • targetConfigs - 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 pages
  • lightning__RecordPage - Record detail pages
  • lightning__HomePage - Home pages
  • lightningCommunity__Page - Experience Cloud pages
  • lightningCommunity__Default - Experience Cloud default pages
  • lightning__FlowScreen - Flow screens

TargetConfigs

Usage: Define different property configurations for different targets.

Guidelines:

  • Use targetConfigs to customize properties per target
  • Use datasource for property values with predefined options
  • Use role="inputOnly" for Flow input-only properties
  • Provide meaningful label and description for each property

Property Types:

  • String - Text values
  • Boolean - True/false values
  • Integer - Numeric values
  • Object - Complex objects

Component Metadata Best Practices

  • Always provide meaningful masterLabel and description
  • Define appropriate targets for component usage
  • Use targetConfigs to customize properties per target
  • Provide helpful description text for each property
  • Use datasource for properties with predefined options
  • Keep API versions consistent

Import Organization

Import Order

Guidelines:

  1. Lightning Web Component imports (lwc)
  2. Lightning platform imports (lightning/*)
  3. Salesforce imports (@salesforce/*)
  4. Third-party library imports
  5. 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:

  1. Developer edits SASS/SCSS files in __styles__/ folder
  2. Build process compiles SASS/SCSS to CSS
  3. Compiled CSS file (componentName.css) is generated in main component directory
  4. 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 :host selector
  • 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:

  1. Use Lightning components first - lightning-button, lightning-layout, lightning-input, etc.
  2. Use SLDS classes as fallback - When Lightning components don't exist or don't meet specific requirements
  3. 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 :host for component-level styles
  • Use element selectors for child elements
  • Avoid global styles (use :host instead)
  • 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 @import to 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 :host for 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:if to 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=true for 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:if to conditionally render elements
  • Avoid unnecessary re-renders
  • Use lwc:key with lwc:for for 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:key with 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 innerHTML with 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:if directly on the element (no template wrapper needed)
  • Multiple elements: Wrap in <template> tag with lwc: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:

  1. Documentation - Document why the code is deprecated and what to use instead
  2. Transition Period - Provide a grace period for migration
  3. 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:false to lwc:if when 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