LWC Component API
This page is organized from state surface to behavior surface: property decorators first, then public methods, then internal function patterns, and finally computed properties for template-facing values.
Property Decorators
Private Property Conventions
Standard: Underscore Prefix (_) for Backing Properties
Use the underscore prefix (_) convention exclusively for backing properties behind @api getter/setter pairs. The _ prefix is not a general "private" marker -- it specifically signals a property that stores the internal value for a public API.
Properties without @api are already private by default in LWC, so the _ prefix is only needed when a corresponding public getter/setter exists.
Do not use _ on methods. The absence of @api is the sole indicator that a method is private. See Methods for naming conventions.
Example:
export default class ExampleParent extends LightningElement {
// Backing property for the public customClass API
_customClass = 'default-class';
@api get customClass() {
return this._customClass;
}
set customClass(value) {
this._customClass = value || 'default-class';
}
// Private reactive property (no underscore, no @api)
isLoading = false;
// Private method (no underscore, no @api)
setColorClasses() {
// ...
}
}
Scope rules for _ properties:
- Only reference them within the same JavaScript file
- Never access them from templates, other files, or parent/child components
- Expose their values through public getters/setters instead
Why not # (ES2022 private fields)?
LWC already treats every non-@api member as private to consumers. The framework defines encapsulation through @api, so # private fields do not add meaningful protection for typical component work.
Beyond the lack of benefit, # fields interact poorly with LWC's architecture and are not recommended:
- Templates are compiled separately from the component class. Because
#names are lexically scoped to the class body, the compiled template code cannot bind to them the way it binds to ordinary properties and getters. - The LWC component instance and the custom element are separate objects. The framework proxies
@apimembers to the host element at runtime but has no mechanism to intercept#private members. - Code that uses
#may compile in narrow cases but risks surprising failures around template access, inheritance, and tooling that expects conventional LWC patterns.
This is an ongoing architectural discussion in the LWC repository.
@api Decorator
Usage: Use @api to expose public properties and methods that can be set by a parent component or used in the component configuration.
Guidelines:
- Use
@apifor properties that need to be configurable from outside the component - Use
@apifor methods that need to be callable from parent components - Properties decorated with
@apiare reactive by default - Use getters/setters when transformation or validation is needed
Direct @api Properties:
Use direct @api properties when no transformation or validation is needed:
// Good: Simple property, no transformation needed
@api recordId;
@api label = 'Default Label';
@api disabled = false;
@api iconName;
@api Getters/Setters:
Use getters/setters when you need to transform, validate, or perform side effects:
// Good: Transformation needed
// Note: _customClass is private - only accessed within this file
@api get customClass() {
return this._customClass;
}
set customClass(value) {
this._customClass = value || DEFAULT_CLASS;
this.classList.add(...this._customClass.split(' '));
}
// Good: Validation needed
// Note: _borderRadius is private - only accessed within this file
@api get borderRadius() {
return this._borderRadius;
}
set borderRadius(value) {
if (value && isValidStyle(value, 'borderRadius')) {
this._cssVariables['--example-child-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>
Good Example (Using Public API in Template):
<!-- Good: Using public property/getter -->
<p class={customClass}>Content</p>
// Good: Template uses public API, private property stays internal
@api get customClass() {
return this._customClass;
}
@track Decorator
Usage: Use @track when you need the engine to observe in-place mutations of a plain object or array held in a field (nested properties, push, index assignment, and so on). Starting in API 48 / Spring '20, class fields are reactive by default for reassignment (for example this.isLoading = true), so @track is unnecessary for primitives and for object/array fields when you always replace the whole value.
Guidelines:
- API 48 / Spring '20 and later: Fields on the component class react to new assignments; primitives are compared with
===. - Plain objects and arrays without
@track: Only reassigning the field triggers an update. Mutatingthis.config.enabledorthis.items.push(item)does not rerender unless the field is@trackor you assign a new object/array to the field. - Prefer immutable updates (new object/array reference) for clarity and predictable rerenders; use
@trackwhen you intentionally mutate in place. @trackdoes not deeply observeDate,Map,Set, or class instances- only plain
{}and[]. After changing those types, reassign the field
or expose a primitive/getter the template can read. The runtime may log a non-trackable object warning when@trackis applied to an unsupported value.
- only plain
See: Reactivity for fields, objects, and arrays.
When to Use @track:
// Good: in-place mutation of a plain object is observed
@track config = {
enabled: true,
size: 'medium'
};
// Good: in-place array mutation is observed (immutable updates are still preferred)
@track items = [];
// Unnecessary since API 48 / Spring '20 — reassignment is enough for primitives
// @track isLoading = false;
Preferred Pattern (immutable updates — works with or without @track):
// Without @track: reassign the field so nested changes rerender
updateConfig(newValue) {
this.config = {
...this.config,
enabled: newValue
};
}
addItem(item) {
this.items = [...this.items, item];
}
@wire Decorator
Usage: Use @wire to read Salesforce data reactively. The wire service provides data to the component automatically.
Guidelines:
- Use
@wirefor reactive data that should update automatically - Wire adapters include:
getRecord,getRecordUi,getObjectInfo,getPicklistValues, Apex methods, and message channels - Wire properties are read-only - don't assign to them directly
- Handle wire errors appropriately
Examples:
import { wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import { MessageContext } from 'lightning/messageService';
import getAccountData from '@salesforce/apex/AccountController.getAccountData';
// Wire to Lightning Data Service
@wire(getRecord, { recordId: '$recordId', fields: ACCOUNT_FIELDS })
wiredAccount({ error, data }) {
if (data) {
this.account = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.account = undefined;
}
}
// Wire to Apex method
@wire(getAccountData, { accountId: '$recordId' })
wiredAccountData({ error, data }) {
if (data) {
this.accountData = data;
} else if (error) {
this.handleError(error);
}
}
// Wire to Message Channel
@wire(MessageContext)
messageContext;
See: Data and Platform for detailed wire adapter patterns.
Tracked vs Reactive Properties
Class fields and assignment (API 48 / Spring '20 and later):
Fields declared on the component class participate in reactivity when you assign a new value. Primitives and whole-object/array replacement do not require @track.
// Rerender when values change if the template (or a getter used by it) reads them
@api recordId;
label = 'Default';
isLoading = false;
items = []; // element changes: use @track or immutable list updates
When the UI rerenders:
A rerender runs when a change affects something the template read in the previous render cycle—either a field referenced in the template or data reached through a getter the template uses. With @track, mutations that do not affect rendered paths may still be ignored.
Details: Reactivity for fields, objects, and arrays.
What is not reactive:
- Local variables and other values scoped inside methods (not component class fields)
- Static properties on the class
- Expando properties added to objects at runtime
Best practice:
- Prefer new object/array references over in-place mutation unless
@trackis a deliberate choice. - Use
@trackonly when relying on deep mutation of a plain object or array. - For
Date,Map,Set, or custom class instances, reassign the field after meaningful change instead of expecting deep tracking.
Public Methods
Public methods are the component's explicit behavior contract with parent components.
When to Expose Public Methods
Expose @api methods when:
- Parent components need to control child behavior - Focus, click, reset, etc.
- Component needs to be controlled externally - Show/hide, enable/disable programmatically
- Utility methods that parent components need - Validation, data retrieval, etc.
Examples:
// Good: Focus method for parent to control focus
@api focus() {
const button = this.template.querySelector('button');
if (button) {
button.focus();
}
}
// Good: Click method for parent to trigger click
@api click() {
const button = this.template.querySelector('button');
if (button) {
button.click();
}
}
// Good: Reset method for parent to reset component state
@api reset() {
this.value = '';
this.error = undefined;
this.isDirty = false;
}
// Good: Validation method for parent to check validity
@api validate() {
if (!this.value) {
this.error = 'Value is required';
return false;
}
this.error = undefined;
return true;
}
Public Method Patterns
When Connection Checks Are Needed:
For synchronous public @api methods, connection checks are typically not necessary because:
- Parent components can only call child methods when the child is connected
- LWC ensures components are connected before they can be interacted with
- Event handlers are only called when components are connected
Connection checks ARE needed for:
- Async operations - Promises, setTimeout, setInterval callbacks that might complete after disconnection
- Wire adapter callbacks - Can sometimes fire after disconnection in edge cases (though LWC usually handles this)
Examples:
// Not needed: Synchronous public method (component is inherently connected)
@api focus() {
const button = this.template.querySelector('button');
if (button) { // Check element existence, not connection
button.focus();
}
}
// 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;
}
}
// Track connection state (only needed if you use connection checks)
connectedCallback() {
this._connected = true;
}
disconnectedCallback() {
this._connected = false;
}
Return Values:
Public methods can return values for parent components:
// Good: Return validation result
@api validate() {
if (!this.value) {
return { isValid: false, error: 'Value is required' };
}
return { isValid: true, error: undefined };
}
// Good: Return current state
@api getState() {
return {
value: this.value,
isValid: this.isValid,
isDirty: this.isDirty
};
}
Public Method Naming
- Use verb-based names:
focus(),click(),reset(),validate(),getState() - Be descriptive about what the method does
- Avoid generic names like
doSomething()orhandle()
Function and Method Patterns
In LWC classes, the meaningful choice is between regular methods (prototype) and class fields that hold arrow functions (often called "arrow methods"). Arrow functions used in modules, .map() / .then(), or other inline callbacks are the same language feature in a different place. Prefer regular methods as the default; use arrow class fields when you pass the function as a value and the caller will not preserve this.
LWC templates: Handlers like onclick={handleSave} invoke your method with the correct component instance. You do not need an arrow class field for template wiring alone.
Regular class methods
Syntax: methodName() { ... } (no arrow on the class body).
- Where they live: On the class prototype - one function shared by all instances.
this: Set by how the function is called (LWC sets it correctly for template-bound handlers).- Use for: Default component logic, helpers called from other methods, and anything the framework must recognize by name.
- Required for:
constructor, lifecycle hooks (connectedCallback,disconnectedCallback, etc.), and@apimethods (must be prototype methods).
import { api, LightningElement } from 'lwc';
export default class Example extends LightningElement {
connectedCallback() {
this.initialize();
}
initialize() {
// Internal logic
}
handleSave() {
if (!this.isValid()) {
return;
}
this.persist();
}
isValid() {
return true;
}
persist() {
// ...
}
@api
refreshData() {
// @api must be a regular method
}
}
Arrow class fields ("arrow methods")
Syntax: methodName = () => { ... } (class field whose value is an arrow function).
- Where they live: A new function per instance (slightly more memory than prototype methods; usually fine unless you have very large instance counts).
this: Lexical - always the component instance, even if the function is stored and called later (e.g. from an object map or timer).- Use when: You pass
this.someMethodas a callback and the consumer calls it without a boundthis, or you keep handlers in a map keyed by name.
export default class Example extends LightningElement {
handleAction(event) {
const actions = {
save: this.save,
remove: this.remove
};
actions[event.detail.action]?.();
}
save = () => {
// `this` is still the component when invoked from `actions`
};
remove = () => {
// same
};
}
Avoid arrow class fields for bulk reusable logic that does not need lexical this; use a regular method instead so the function stays on the prototype.
Arrow functions in modules and callbacks
Outside the class, arrow functions are normal JavaScript: shared utilities (named const exports), array / promise callbacks, and inline callbacks where you want lexical this from the enclosing method (e.g. setTimeout(() => this.tick(), 1000)).
// c/utils.js — stateless helpers
export const formatDate = (dateString) => {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
return new Date(dateString).toLocaleDateString(undefined, options);
};
import { LightningElement } from 'lwc';
export default class Example extends LightningElement {
accounts = [];
handleFilter() {
const names = this.accounts
.filter((account) => account.Revenue > 1_000_000)
.map((account) => account.Name);
// use names...
}
startTimer() {
setTimeout(() => {
// lexical `this` from enclosing method scope
console.log(this.accounts?.length);
}, 1000);
}
}
Prefer explicit parameters or rest (...args) instead of the legacy arguments object in new code.
At a glance
| Topic | Regular method | Arrow class field |
|---|---|---|
this |
Dynamic (caller sets it; LWC sets it for template handlers) | Lexical (always instance) |
| Storage | Prototype (shared) | Per instance |
@api / lifecycle |
Required | Not valid for these |
| Typical use | Default for component logic | Passing methods into maps, timers, or APIs that do not bind this |
Reference:
Computed Properties
After defining public/private behavior, use computed properties to shape values consumed by templates.
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
Change History
Version 1.0 - 2026-03-26
- Added initial version history tracking for this document.
Last Updated: 2026-03-26 Version: 1.0