| name | salesforce-component-standards |
|---|---|
| description | Quality standards for Salesforce Lightning Web Components (LWC), Aura components, and Visualforce pages. Covers SLDS 2 compliance, accessibility (WCAG 2.1 AA), data access pattern selection, component communication rules, XSS prevention, CSRF enforcement, FLS/CRUD in AuraEnabled methods, view state management, and Jest test requirements. Use this skill when building or reviewing any Salesforce UI component to enforce platform-specific security and quality standards. |
Apply these checks to every LWC, Aura component, and Visualforce page you write or review.
Choose the right data access pattern before writing JavaScript controller code:
| Use case | Pattern | Why |
|---|---|---|
| Read a single record reactively (follows navigation) | @wire(getRecord, { recordId, fields }) |
Lightning Data Service — cached, reactive |
| Standard CRUD form for a single object | <lightning-record-form> or <lightning-record-edit-form> |
Built-in FLS, CRUD, and accessibility |
| Complex server query or filtered list | @wire(apexMethodName, { param }) on a cacheable=true method |
Allows caching; wire re-fires on param change |
| User-triggered action, DML, or non-cacheable server call | Imperative apexMethodName(params).then(...).catch(...) |
Required for DML — wired methods cannot be @AuraEnabled without cacheable=true |
| Cross-component communication (no shared parent) | Lightning Message Service (LMS) | Decoupled, works across DOM boundaries |
| Multi-object graph relationships | GraphQL @wire(gql, { query, variables }) |
Single round-trip for complex related data |
| Rule | Enforcement |
|---|---|
No raw user data in innerHTML |
Use {expression} binding in the template — the framework auto-escapes. Never use this.template.querySelector('.el').innerHTML = userValue |
Apex @AuraEnabled methods enforce CRUD/FLS |
Use WITH USER_MODE in SOQL or explicit Schema.sObjectType checks |
| No hardcoded org-specific IDs in component JavaScript | Query or pass as a prop — never embed record IDs in source |
@api properties from parent: validate before use |
A parent can pass anything — validate type and range before using as a query parameter |
- Never hardcode colours:
color: #FF3366→ usecolor: var(--slds-c-button-brand-color-background)or a semantic SLDS token. - Never override SLDS classes with
!important— compose with custom CSS properties. - Use
<lightning-*>base components wherever they exist:lightning-button,lightning-input,lightning-datatable,lightning-card, etc. - Base components include built-in SLDS 2, dark mode, and accessibility — avoid reimplementing their behaviour.
- If using custom CSS, test in both light mode and dark mode before declaring done.
Every LWC component must pass all of these before it is considered done:
- All form inputs have
<label>oraria-label— never use placeholder as the only label - All icon-only buttons have
alternative-textoraria-labeldescribing the action - All interactive elements are reachable and operable by keyboard (Tab, Enter, Space, Escape)
- Colour is not the only means of conveying status — pair with text, icon, or
aria-*attributes - Error messages are associated with their input via
aria-describedby - Focus management is correct in modals — focus moves into the modal on open and back on close
| Direction | Mechanism |
|---|---|
| Parent → Child | @api property or calling a @api method |
| Child → Parent | CustomEvent — this.dispatchEvent(new CustomEvent('eventname', { detail: data })) |
| Sibling / unrelated components | Lightning Message Service (LMS) |
| Never use | document.querySelector, window.*, or Pub/Sub libraries |
For Flow screen components:
- Events that need to reach the Flow runtime must set
bubbles: trueandcomposed: true. - Expose
@api valuefor two-way binding with the Flow variable.
- No side effects in
connectedCallback: it runs on every DOM attach — avoid DML, heavy computation, or rendering state mutations here. - Guard
renderedCallback: always use a boolean guard to prevent infinite render loops. - Avoid reactive property traps: setting a reactive property inside
renderedCallbackcauses a re-render — use it only when necessary and guarded. - Do not store large datasets in component state — paginate or stream large results instead.
Every component that handles user interaction or retrieves Apex data must have a Jest test:
// Minimum test coverage expectations
it('renders the component with correct title', async () => { ... });
it('calls apex method and displays results', async () => { ... }); // Wire mock
it('dispatches event when button is clicked', async () => { ... });
it('shows error state when apex call fails', async () => { ... }); // Error pathUse @salesforce/sfdx-lwc-jest mocking utilities:
wireadapter mocking:setImmediate+emit({ data, error })- Apex method mocking:
jest.mock('@salesforce/apex/MyClass.myMethod', ...)
- New components: always LWC unless the target context is Aura-only (e.g. extending
force:appPage, using Aura-specific events in a legacy managed package). - Migrating Aura to LWC: prefer LWC, migrate component-by-component; LWC can be embedded inside Aura components.
@AuraEnabledcontroller methods must declarewith sharingand enforce CRUD/FLS — Aura does not enforce them automatically.- Never use
{!v.something}with unescaped user data in<div>unbound helpers — use<ui:outputText value="{!v.text}" />or<c:something>to escape. - Validate all inputs from component attributes before using them in SOQL / Apex logic.
- Component events for parent-child communication — lowest scope.
- Application events only when component events cannot reach the target — they broadcast to the entire app and can be a performance and maintenance problem.
- For hybrid LWC + Aura stacks: use Lightning Message Service to decouple communication — do not rely on Aura application events reaching LWC components.
<!-- ❌ NEVER — renders raw user input as HTML -->
<apex:outputText value="{!userInput}" escape="false" />
<!-- ✅ ALWAYS — auto-escaping on -->
<apex:outputText value="{!userInput}" />
<!-- Default escape="true" — platform HTML-encodes the output -->Rule: escape="false" is never acceptable for user-controlled data. If rich text must be rendered, sanitise server-side with a whitelist before output.
Use <apex:form> for all postback actions — the platform injects a CSRF token automatically into the form. Do not use raw <form method="POST"> HTML elements, which bypass CSRF protection.
// ❌ NEVER
String soql = 'SELECT Id FROM Account WHERE Name = \'' + ApexPages.currentPage().getParameters().get('name') + '\'';
List<Account> results = Database.query(soql);
// ✅ ALWAYS — bind variable
String nameParam = ApexPages.currentPage().getParameters().get('name');
List<Account> results = [SELECT Id FROM Account WHERE Name = :nameParam];- View state is under 135 KB (check in browser developer tools or the Salesforce View State tab)
- Fields used only for server-side calculations are declared
transient - Large collections are not persisted across postbacks unnecessarily
-
readonly="true"is set on<apex:page>for read-only pages to skip view-state serialisation
// Before reading a field
if (!Schema.sObjectType.Account.fields.Revenue__c.isAccessible()) {
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'You do not have access to this field.'));
return null;
}
// Before performing DML
if (!Schema.sObjectType.Account.isDeletable()) {
throw new System.NoAccessException();
}Standard controllers enforce FLS for bound fields automatically. Custom controllers do not — FLS must be enforced manually.
| Anti-pattern | Technology | Risk | Fix |
|---|---|---|---|
innerHTML with user data |
LWC | XSS | Use template bindings {expression} |
| Hardcoded hex colours | LWC/Aura | Dark-mode / SLDS 2 break | Use SLDS CSS custom properties |
Missing aria-label on icon buttons |
LWC/Aura/VF | Accessibility failure | Add alternative-text or aria-label |
No guard in renderedCallback |
LWC | Infinite rerender loop | Add hasRendered boolean guard |
| Application event for parent-child | Aura | Unnecessary broadcast scope | Use component event instead |
escape="false" on user data |
Visualforce | XSS | Remove — use default escaping |
Raw <form> postback |
Visualforce | CSRF vulnerability | Use <apex:form> |
No with sharing on custom controller |
VF / Apex | Data exposure | Add with sharing declaration |
| FLS not checked in custom controller | VF / Apex | Privilege escalation | Add Schema.sObjectType checks |
| SOQL concatenated with URL param | VF / Apex | SOQL injection | Use bind variables |