lwc-dynamic-components
Dynamic LWC component creation using the `lwc:component` directive, lazy-loaded dynamic imports (`import()`), and runtime component resolution for conditional rendering at scale. Triggers: 'render different components based on record type', 'dynamically load lwc at runtime', 'lwc:component lwc:is constructor', 'lazy load component only when needed', 'dynamic import lwc'. NOT for static component composition or `lwc:if` conditional rendering when the component set is fixed at build time (use lwc-conditional-rendering).
Best use case
lwc-dynamic-components is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Dynamic LWC component creation using the `lwc:component` directive, lazy-loaded dynamic imports (`import()`), and runtime component resolution for conditional rendering at scale. Triggers: 'render different components based on record type', 'dynamically load lwc at runtime', 'lwc:component lwc:is constructor', 'lazy load component only when needed', 'dynamic import lwc'. NOT for static component composition or `lwc:if` conditional rendering when the component set is fixed at build time (use lwc-conditional-rendering).
Teams using lwc-dynamic-components should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/lwc-dynamic-components/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How lwc-dynamic-components Compares
| Feature / Agent | lwc-dynamic-components | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Dynamic LWC component creation using the `lwc:component` directive, lazy-loaded dynamic imports (`import()`), and runtime component resolution for conditional rendering at scale. Triggers: 'render different components based on record type', 'dynamically load lwc at runtime', 'lwc:component lwc:is constructor', 'lazy load component only when needed', 'dynamic import lwc'. NOT for static component composition or `lwc:if` conditional rendering when the component set is fixed at build time (use lwc-conditional-rendering).
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# LWC Dynamic Components
Use this skill when a Lightning Web Component must select and render a different child component at runtime — not at build time — based on record type, user role, server-side configuration, or a feature flag that cannot be resolved statically. The `lwc:component` directive combined with dynamic `import()` is the platform-supported mechanism for this pattern since Spring '23.
---
## Before Starting
Gather this context before working on anything in this domain:
- Is the component deployed in a **managed package** or an **unlocked package**? Dynamic imports require managed-package context; unlocked packages do not support the `lwc:component` dynamic loading pattern.
- Is **Lightning Web Security (LWS)** enabled? Dynamic imports are not available in the legacy Locker Service environment. LWS is required.
- What is the complete set of components that may be rendered? Dynamic imports use static string paths resolved at bundle time — you cannot construct the module specifier from a variable at runtime.
- Does the selected component receive `@api` properties, and which ones? Property passing is straightforward but must be handled after the constructor is set.
- Is there an error path if the module load fails? A missing `try/catch` leaves the user with a blank area and no feedback.
---
## Core Concepts
### The `lwc:component` Directive and `lwc:is`
The `lwc:component` tag is a special host element introduced in Spring '23 that renders whatever constructor is assigned to its `lwc:is` property. The template syntax is:
```html
<lwc:component lwc:is={dynamicCtor}></lwc:component>
```
`lwc:is` must receive a component **constructor** (a class reference), not a string name. Passing a string does nothing and renders no component. When `lwc:is` is `null` or `undefined`, nothing is rendered, which is the correct initial state before the import resolves.
### Dynamic Import and the `default` Export
The `import()` function loads a module asynchronously and returns a Promise that resolves to the module object. LWC component modules export their class as the **default export**, so you must destructure `default` to get the constructor:
```js
// Correct
const { default: Ctor } = await import('c/myComponent');
this.dynamicCtor = Ctor;
// Also correct (explicit)
const module = await import('c/myComponent');
this.dynamicCtor = module.default;
// Wrong — this sets lwc:is to the module object, not the constructor
this.dynamicCtor = await import('c/myComponent');
```
Assign the constructor to a reactive property decorated with `@track` or declared as a class field so the template re-evaluates when it changes.
### Lazy Loading and Bundle Splitting
Dynamic imports cause the module to be loaded as a **separate chunk** at runtime rather than included in the page's initial JavaScript bundle. This is the main performance benefit: a large component that is only sometimes needed does not inflate the first-paint payload. The component is fetched the first time `import()` is called; subsequent imports of the same module resolve from cache.
This also means there is a **network round-trip** the first time the component loads. For components that are almost always needed, a static import is cheaper. Reserve dynamic import for components that are genuinely conditional and large enough to justify the separate fetch.
### Property Passing and Event Handling
After setting `lwc:is`, you can pass `@api` properties to the dynamic component using standard attribute binding on the `lwc:component` tag. The dynamic child can dispatch custom events; the parent listens with a standard `oneventname` handler on the `lwc:component` element. There is no difference from static composition once the constructor is resolved.
---
## Common Patterns
### Mode 1: Implement a Dynamic Component Renderer
**When to use:** You need to select from a known set of LWC components at runtime based on record type, user role, or server-returned configuration.
**How it works:**
1. Declare a reactive property for the constructor and an optional map for `@api` inputs:
```js
import { LightningElement, api, track } from 'lwc';
export default class DynamicFormRouter extends LightningElement {
@api recordTypeId;
@track dynamicCtor = null;
@track componentProps = {};
async connectedCallback() {
await this._loadComponent();
}
async _loadComponent() {
try {
let mod;
if (this.recordTypeId === '012ABC') {
mod = await import('c/caseDetailView');
} else if (this.recordTypeId === '012DEF') {
mod = await import('c/orderDetailView');
} else {
mod = await import('c/genericDetailView');
}
this.dynamicCtor = mod.default;
this.componentProps = { recordId: this.recordTypeId };
} catch (err) {
console.error('Failed to load dynamic component', err);
this.dynamicCtor = null;
}
}
}
```
2. In the template, render the dynamic component and bind properties:
```html
<template>
<template lwc:if={dynamicCtor}>
<lwc:component lwc:is={dynamicCtor}
record-id={componentProps.recordId}
onstatuschange={handleStatusChange}>
</lwc:component>
</template>
<template lwc:else>
<lightning-spinner alternative-text="Loading..."></lightning-spinner>
</template>
</template>
```
**Why not the alternative:** Using a chain of `lwc:if` blocks with statically imported components works for a small fixed set, but it instantiates all imported modules in the bundle even when only one renders. Dynamic import defers the unused ones entirely.
### Mode 2: Review an Existing Dynamic Component Implementation
Check for:
- Is `lwc:is` receiving `module.default`, not the raw module object?
- Is the import call inside a `try/catch`? What does the UI show when the import fails?
- Is the reactive property triggering a re-render? Ensure it is a tracked or reactive field.
- Is the package type managed? If this is in an unlocked package, the pattern will not work.
- Is LWS enabled? Check Setup > Session Settings > Use Lightning Web Security.
- Are `@api` properties correctly bound on the `lwc:component` element?
- Does the import specifier use a literal string, not a computed value?
### Mode 3: Troubleshoot a Broken Dynamic Component
Common failure modes and their root cause:
| Symptom | Likely Cause |
|---|---|
| Nothing renders, no error | `lwc:is` is receiving the module object instead of `module.default` |
| `lwc:is` binding ignored | Using this in an unlocked package; dynamic import is not supported |
| TypeError on import | LWS is off; the dynamic import API is not available |
| Blank area with no error | `try/catch` swallowed the error; check the console |
| Props not reaching child | Properties not bound on `<lwc:component>` or `@api` missing in child |
| Import specifier error at build | Module specifier is constructed from a variable; must be a literal string |
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Small fixed set of components, always known at build time | `lwc:if` / `lwc:elseif` with static imports | Simpler, no runtime overhead, works in unlocked packages |
| Large or rarely needed component resolved from metadata at runtime | `lwc:component` with dynamic import | Defers bundle cost; component only loaded when needed |
| Component chosen from a configuration table in a managed package | Dynamic import with a switch/map | Clean separation of routing logic from rendering |
| Unlocked package deployment | Static conditional rendering only | Dynamic import is not supported in unlocked packages |
| Component must fire events back to parent | Standard `oneventname` on `<lwc:component>` | No special wiring needed; events work identically |
| Wire data needed before deciding which component to load | Wire first, then `await import()` in a reactive handler | Wire data is available in time; import is deferred until needed |
---
## Recommended Workflow
Step-by-step instructions for an AI agent or practitioner activating this skill:
1. Gather context — confirm the org edition, relevant objects, and current configuration state
2. Review official sources — check the references in this skill's well-architected.md before making changes
3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
4. Validate — run the skill's checker script and verify against the Review Checklist below
5. Document — record any deviations from standard patterns and update the template if needed
---
## Review Checklist
Run through these before marking work in this area complete:
- [ ] `lwc:is` is assigned `module.default`, not the raw module object.
- [ ] The import specifier is a **literal string**, not a computed or template-literal value.
- [ ] The dynamic import is wrapped in `try/catch` with a visible error state for the user.
- [ ] The reactive property holding the constructor is properly declared (`@track` or class field) so the template re-evaluates.
- [ ] The component runs in a managed package if dynamic imports are in use (unlocked packages are not supported).
- [ ] Lightning Web Security is enabled in the org.
- [ ] `@api` properties are bound directly on the `<lwc:component>` element.
- [ ] A loading state (spinner or skeleton) is shown while the import resolves.
- [ ] Child component events are handled via `oneventname` on the `<lwc:component>` element.
---
## Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
1. **`module.default` omission silently renders nothing** — Assigning the Promise result of `await import('c/foo')` directly to `lwc:is` passes a module namespace object, not a constructor. The component silently renders nothing with no console error in most orgs. Always destructure `default`.
2. **Unlocked packages do not support dynamic import** — The `lwc:component` dynamic loading pattern is supported only in managed packages. Teams building in unlocked packages who encounter this will see a runtime failure. There is no workaround short of switching package type.
3. **The import specifier must be a static string** — The LWC build toolchain analyzes import calls at compile time to build the module chunk graph. A computed specifier like `` `c/${componentName}` `` will cause a build error or be silently ignored. All candidate components must appear as literal import strings in the source.
4. **Dynamic imports are not prefetched** — Unlike static imports that are included in the page bundle, dynamic imports fetch their chunk on the first call. This introduces a round-trip latency the first time the user triggers the load. Show a loading indicator and consider whether the round-trip cost is acceptable.
5. **LWS must be enabled** — Legacy Locker Service does not support `import()`. If an org has not migrated to Lightning Web Security, dynamic components will fail with a runtime error. Check Setup > Session Settings.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| Dynamic component router | JavaScript class with async `connectedCallback` or reactive handler wiring `lwc:is` from a `module.default` assignment |
| Template with lwc:component | HTML template binding `lwc:is`, `@api` props, and event handlers on the dynamic element |
| Error handling wrapper | `try/catch` block with fallback state for failed module loads |
| Checker report | File-level findings from `scripts/check_lwc_dynamic.py` for common misconfigurations |
---
## Related Skills
- `lwc/lwc-performance` — use when the goal is general bundle optimization and the dynamic component pattern is one of several options being weighed.
- `lwc/lwc-conditional-rendering` — use when the component set is known at build time and `lwc:if` chains are the right tool.
- `lwc/lwc-in-flow-screens` — use when the dynamic component needs to run inside a Flow screen component context.Related Skills
dynamic-sharing-recalculation
Force or orchestrate sharing recalculation after bulk data loads, rule changes, or user/role reorgs so row access catches up with policy. NOT for designing new sharing rules — use sharing-selection tree.
lwc-web-components-interop
LWC interop with non-LWC web components: consuming third-party standard custom elements in LWC, exposing LWC as custom elements externally, Shadow DOM vs native web components, polyfills, and slotting patterns. NOT for LWC-to-LWC composition (use lwc-best-practices). NOT for Aura interop (use aura-to-lwc-migration).
experience-cloud-lwc-components
Use when building custom LWC components for Experience Cloud (Experience Builder sites, LWR portals, Aura-based communities). Covers community context imports, guest user Apex access patterns, navigation API differences between LWR and Aura, and JS-meta.xml target configuration for Experience Builder exposure. NOT for internal LWC components deployed to Lightning App Builder or standard record pages (see lwc/lwc-development). NOT for Aura community components. Trigger keywords: build LWC for Experience Cloud, custom component community portal LWC, guest user LWC component, community context import salesforce, lightningCommunity target, @salesforce/community, guest Apex.
commerce-lwc-components
Use this skill when building or customizing Lightning Web Components for B2B Commerce or D2C LWR storefronts — product display tiles, cart line-item components, checkout step components, wishlist buttons, and product comparison widgets that rely on Commerce Storefront wire adapters from the commerce namespace. NOT for standard LWC development outside a Commerce store context, not for Aura-based Community Builder components, and not for legacy B2B Commerce (CloudCraze) Aura widgets.
flow-screen-lwc-components
Design custom Lightning Web Components that render inside Screen Flow steps — covers the lightning__FlowScreen target, @api properties as Flow inputs/outputs, FlowAttributeChangeEvent propagation, FlowNavigationNext/Back/Finish/Pause events, the @api validate() hook, and design-time targetConfig wiring. NOT for custom property editors that configure flow elements at design time — see flow-custom-property-editors. NOT for the LWC implementation deep-dive — see lwc/lwc-in-flow-screens.
flow-reactive-screen-components
Build reactive Flow screens where one component updates another without navigation using reactive formulas and component outputs. NOT for Aura-based screens.
flow-dynamic-choices
Build picklists and choice sets in Flow Builder sourced from records, picklist fields, or collections, including dependent choices. NOT for static hard-coded choice sets.
analytics-embedded-components
Use this skill when embedding a CRM Analytics dashboard into a Lightning App Builder page, Experience Cloud page, or when embedding a custom LWC inside an Analytics dashboard. Trigger keywords: wave-wave-dashboard-lwc, wave-community-dashboard, analytics__Dashboard target, embed dashboard, record-id context filtering, dashboard state JSON, analytics component. NOT for standard LWC development unrelated to Analytics dashboards, NOT for designing dashboard content or datasets, NOT for CRM Analytics app creation or dataset management.
health-cloud-lwc-components
Use this skill when building custom LWC components for Health Cloud: extending patient card configurations, building custom timeline components using TimelineObjectDefinition metadata, creating care plan visualizations, and surfacing clinical data via LWC. NOT for standard LWC development unrelated to Health Cloud clinical components, or OmniStudio FlexCard development.
dynamic-apex
Use when building or reviewing code that constructs SOQL/SOSL at runtime, inspects schema metadata via Schema.describe methods, accesses fields dynamically on sObjects, or performs runtime type inspection. Triggers: 'Database.query', 'Schema.getGlobalDescribe', 'Schema.describeSObjects', 'dynamic field access', 'SObjectType', 'DescribeFieldResult'. NOT for static SOQL queries or query performance tuning — use soql-fundamentals or soql-query-optimization.
apex-dynamic-soql-binding-safety
Safe construction of dynamic SOQL — Database.query bind variables (:varName, API 60+ semantics), Database.queryWithBinds(query, Map<String,Object>, AccessLevel) (API 55+), field-name allowlisting, ORDER BY direction whitelist, LIMIT/OFFSET typing, and the interaction with WITH USER_MODE / WITH SECURITY_ENFORCED. NOT for static SOQL — see apex-soql-fundamentals. NOT for FLS enforcement on results — see soql-security or apex-stripinaccessible-and-fls-enforcement.
dynamic-forms-migration
Migrating standard Lightning Record Page layouts (Page Layouts on the Record Detail component) to Dynamic Forms — converting fields and sections to component-level placement, replacing record-type-driven layouts with field-visibility filters, retaining or replacing Quick Action layouts, planning custom-object vs standard-object rollout, and verifying field-level security and Reading View parity. NOT for Dynamic Forms initial setup (use admin/dynamic-forms-and-actions) or Lightning Record Page design from scratch (use admin/lightning-app-builder-advanced).