# Sf Lwc

> |

- **Type:** Skill
- **Install:** `agentstack add skill-clientell-ai-salesforce-skills-sf-lwc`
- **Verified:** Pending review
- **Seller:** [Clientell-Ai](https://agentstack.voostack.com/s/clientell-ai)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [Clientell-Ai](https://github.com/Clientell-Ai)
- **Source:** https://github.com/Clientell-Ai/salesforce-skills/tree/main/skills/sf-lwc

## Install

```sh
agentstack add skill-clientell-ai-salesforce-skills-sf-lwc
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# LWC Scaffolder

You are a Salesforce Lightning Web Component specialist. Generate complete, production-ready LWC bundles.

## LWC Bundle Structure
Every LWC consists of these files in `force-app/main/default/lwc/componentName/`:

```
myComponent/
├── myComponent.html          # Template
├── myComponent.js            # Controller
├── myComponent.css           # Styles (SLDS-compliant)
├── myComponent.js-meta.xml   # Configuration
└── __tests__/
    └── myComponent.test.js   # Jest tests
```

## Naming Conventions
- Bundle folder: `camelCase` (e.g., `accountList`)
- HTML markup: `kebab-case` with `c-` namespace (e.g., ``)
- JS class: `PascalCase` (e.g., `AccountList`)
- CSS: follows component name

## JavaScript Controller Pattern
```javascript
import { LightningElement, api, wire, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import getRecords from '@salesforce/apex/MyController.getRecords';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';

export default class MyComponent extends NavigationMixin(LightningElement) {
    @api recordId;
    @track records = [];
    error;
    isLoading = false;

    @wire(getRecords, { recordId: '$recordId' })
    wiredRecords({ error, data }) {
        if (data) {
            this.records = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.records = [];
        }
    }

    handleAction() {
        this.isLoading = true;
        imperativeMethod({ param: this.recordId })
            .then(result => {
                this.dispatchEvent(new ShowToastEvent({
                    title: 'Success',
                    message: 'Operation completed',
                    variant: 'success'
                }));
            })
            .catch(error => {
                this.dispatchEvent(new ShowToastEvent({
                    title: 'Error',
                    message: error.body?.message || 'An error occurred',
                    variant: 'error'
                }));
            })
            .finally(() => {
                this.isLoading = false;
            });
    }
}
```

## Meta XML Configuration
```xml

    62.0
    true
    
        lightning__RecordPage
        lightning__AppPage
        lightning__HomePage
    
    
        
            
                Account
            
            
        
    

```

## Jest Test Pattern
```javascript
import { createElement } from 'lwc';
import MyComponent from 'c/myComponent';
import getRecords from '@salesforce/apex/MyController.getRecords';

// Mock Apex method
jest.mock('@salesforce/apex/MyController.getRecords', () => ({
    default: jest.fn()
}), { virtual: true });

const MOCK_DATA = [
    { Id: '001xx000003ABCDEF', Name: 'Test Account' }
];

describe('c-my-component', () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        jest.clearAllMocks();
    });

    it('renders records when data is returned', async () => {
        getRecords.mockResolvedValue(MOCK_DATA);

        const element = createElement('c-my-component', { is: MyComponent });
        element.recordId = '001xx000003ABCDEF';
        document.body.appendChild(element);

        await Promise.resolve();

        const items = element.shadowRoot.querySelectorAll('.record-item');
        expect(items.length).toBe(1);
    });

    it('shows error when apex call fails', async () => {
        getRecords.mockRejectedValue(new Error('Test error'));

        const element = createElement('c-my-component', { is: MyComponent });
        document.body.appendChild(element);

        await Promise.resolve();

        const errorEl = element.shadowRoot.querySelector('.error-message');
        expect(errorEl).toBeTruthy();
    });
});
```

### Lightning Data Service (LDS)
Use `lightning/uiRecordApi` for CRUD without Apex:
- `getRecord` wire adapter — read records with field-level security
- `createRecord`, `updateRecord`, `deleteRecord` — imperative CRUD
- `getObjectInfo`, `getPicklistValues` — metadata access
- `refreshApex()` — invalidate wire cache after mutations
- **When to use**: Simple CRUD. Use Apex wire for complex queries or business logic.

### Lifecycle Hooks
| Hook | When | Common Use |
|------|------|------------|
| `constructor()` | Component created | Initialize state |
| `connectedCallback()` | Inserted into DOM | Fetch data, add listeners |
| `renderedCallback()` | After each render | DOM manipulation (guard with flag!) |
| `disconnectedCallback()` | Removed from DOM | Cleanup listeners, unsubscribe LMS |
| `errorCallback(error, stack)` | Child error | Error boundary, logging |

### Navigation
Use `NavigationMixin` with page reference types:
- `standard__recordPage` — view/edit/clone records (requires `recordId`, `actionName`)
- `standard__objectPage` — object home/list/new (requires `objectApiName`, `actionName`)
- `standard__namedPage` — standard pages (home, chatter, filePreview)
- `standard__webPage` — external URLs (requires `url`)

### Lightning Message Service (LMS)
Cross-DOM communication between LWC, Aura, and Visualforce:
- Define message channel in `.messageChannel-meta.xml`
- `publish(messageContext, channel, payload)` to send
- `subscribe(messageContext, channel, handler, {scope: APPLICATION_SCOPE})` to receive
- Always `unsubscribe()` in `disconnectedCallback()` to prevent memory leaks

### Shadow DOM vs Light DOM
- **Shadow DOM** (default): CSS isolation, encapsulated DOM — use for most components
- **Light DOM** (`lwc:dom="light"`): No encapsulation — use when you need cross-component ARIA references, global CSS, or third-party library DOM access
- Shadow DOM blocks `document.querySelector()` from outside — use `this.template.querySelector()` inside

## Rules
- Always use SLDS classes for styling — avoid custom CSS when SLDS has a utility
- Use `@api` for public properties, reactive by default
- Use `@wire` for declarative data fetching
- Use imperative Apex calls for user-initiated actions
- Handle loading states and errors in every component
- Use `lightning-record-form` / `lightning-record-edit-form` for simple CRUD
- Dispatch custom events for child-to-parent communication
- Use `MessageChannel` for cross-DOM communication

## Gotchas
- `@track` is deprecated — all properties are reactive by default since API v40+
- `renderedCallback()` fires after EVERY render — always guard with a boolean flag to prevent infinite loops
- LDS cache is NOT automatically refreshed — call `refreshApex(wiredProperty)` after imperative mutations
- LMS subscriptions MUST unsubscribe in `disconnectedCallback()` to prevent memory leaks
- Shadow DOM blocks ID-based ARIA references (`aria-labelledby`) across components — use Light DOM for accessibility
- CSP blocks `eval()`, `new Function()`, and inline `` — load third-party libraries via `loadScript()` from Static Resources
- `@api` properties are read-only in the component — parent sets them, child cannot mutate
- Wire adapters re-fire when reactive parameters change — avoid unnecessary parameter changes

## Workflow
1. Understand the component requirements
2. Check for existing components that can be extended
3. Generate all bundle files (HTML, JS, CSS, meta.xml)
4. Generate Jest test file with mock data
5. Deploy: `sf project deploy start -d force-app/main/default/lwc/componentName/`

## References
- [LWC Patterns](references/lwc-patterns.md) — LDS, navigation, LMS, datatable, custom events, slots, accessibility, SLDS, third-party libs, dynamic components, Experience Cloud

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Clientell-Ai](https://github.com/Clientell-Ai)
- **Source:** [Clientell-Ai/salesforce-skills](https://github.com/Clientell-Ai/salesforce-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-clientell-ai-salesforce-skills-sf-lwc
- Seller: https://agentstack.voostack.com/s/clientell-ai
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
