lwc-template-refs
Use when an LWC needs a stable, typed handle to a specific DOM element it owns — focusing inputs, imperatively validating a known form field, invoking a `@api` method on a child component, or migrating fragile `this.template.querySelector('.css-class')` code to the modern `lwc:ref` directive. Triggers: 'this.template.querySelector fragile', 'lwc:ref not working inside for:each', 'how to focus an input from lwc', 'refs undefined in connectedcallback', 'migrating querySelector to lwc:ref'. NOT for querying elements inside `for:each` iterators — refs do not work there — and NOT for cross-shadow queries of child custom elements' internals.
Best use case
lwc-template-refs is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when an LWC needs a stable, typed handle to a specific DOM element it owns — focusing inputs, imperatively validating a known form field, invoking a `@api` method on a child component, or migrating fragile `this.template.querySelector('.css-class')` code to the modern `lwc:ref` directive. Triggers: 'this.template.querySelector fragile', 'lwc:ref not working inside for:each', 'how to focus an input from lwc', 'refs undefined in connectedcallback', 'migrating querySelector to lwc:ref'. NOT for querying elements inside `for:each` iterators — refs do not work there — and NOT for cross-shadow queries of child custom elements' internals.
Teams using lwc-template-refs 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-template-refs/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How lwc-template-refs Compares
| Feature / Agent | lwc-template-refs | 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?
Use when an LWC needs a stable, typed handle to a specific DOM element it owns — focusing inputs, imperatively validating a known form field, invoking a `@api` method on a child component, or migrating fragile `this.template.querySelector('.css-class')` code to the modern `lwc:ref` directive. Triggers: 'this.template.querySelector fragile', 'lwc:ref not working inside for:each', 'how to focus an input from lwc', 'refs undefined in connectedcallback', 'migrating querySelector to lwc:ref'. NOT for querying elements inside `for:each` iterators — refs do not work there — and NOT for cross-shadow queries of child custom elements' internals.
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 Template Refs
Use this skill when a component needs a deterministic handle to one of its own DOM elements — the classic "focus this input, call this child's `@api` method, read this element's value" problem — and the existing code leans on `this.template.querySelector('.some-class')`. The `lwc:ref` directive was introduced specifically to replace that brittle pattern with a typed, name-based lookup.
---
## Before Starting
Gather this context first:
- Which element needs a reference, and is it a single owned element or part of a repeated list? Refs only work for single elements — lists belong on a different pattern.
- Where is the ref accessed in the lifecycle? If the code needs to touch the DOM in `connectedCallback`, `this.refs` will be undefined — first render has not happened yet.
- Is the target element wrapped in a `lwc:if` branch? If the branch is false, the ref is undefined, and any code that assumes it exists will throw.
- Is the target inside a child custom element's shadow DOM? `this.refs` stops at the shadow boundary and cannot reach into `<c-child>`'s internals — that is by design.
---
## Core Concepts
Template refs in LWC are a declarative replacement for class-based `querySelector` lookups. They are simple, but timing and scope rules catch people off guard.
### The `lwc:ref` Directive Declares A Named Handle
In the template, mark an element with `lwc:ref="<name>"` — for example `<lightning-input lwc:ref="emailInput">`. In the component's JavaScript, access it with `this.refs.emailInput`. The directive value must be a literal string, must be unique within a given template root, and resolves to the single element that rendered with that name. The directive can be placed on any element, including base components, standard HTML, and child custom components.
### Refs Are Only Available After The First Render
`this.refs` is populated by the rendering engine as part of the render pass. That means it is undefined in `constructor` and `connectedCallback`, and it is only reliably usable in `renderedCallback` and any handler that runs after at least one render — click handlers, input events, setters invoked after mount. Code that touches `this.refs` in `connectedCallback` will throw. The documented workaround is to do the work in `renderedCallback`, usually guarded by a boolean flag so it runs once.
### Refs Respect Shadow Boundaries And Conditional Rendering
`this.refs` only sees elements that belong to this component's own template. It does not traverse into a child custom element's shadow DOM — that is intentional encapsulation, and the correct way to interact with a child is through its `@api` properties, methods, and events. Refs also live and die with conditional rendering: if an element is wrapped in `lwc:if={showPanel}` and `showPanel` is false, `this.refs.<name>` is undefined until the branch becomes true and re-renders.
### Refs Do Not Work Inside Iterators
The LWC documentation is explicit: `lwc:ref` must not be used inside `for:each` or `iterator` templates. The name would collide for every iteration, so the platform does not support it. For per-row interactions use `data-*` attributes on the row element and resolve them with `event.target.closest('[data-id]')` inside a delegated listener, or use `this.template.querySelectorAll(...)` when you truly need the whole list.
### `this.refs` Is A Fresh Lookup, Not A Long-Lived Cache
Each access of `this.refs.<name>` is a fresh read against the current render. Do not stash `const input = this.refs.emailInput` in a class property across renders — re-read on use. Names are also scoped per template root, so the same name can reappear in a different template (for example an inner template returned from `render()`) without collision.
### Migrating From `this.template.querySelector`
For single known elements, the migration is mechanical: add `lwc:ref="x"` to the element, replace the selector call with `this.refs.x`, and move the access out of `connectedCallback` if needed. `this.template.querySelector` is still valid and still required for collections, complex CSS selectors, and iterator children — refs and selectors coexist cleanly.
---
## Common Patterns
### Focus-On-Open With A Render Guard
**When to use:** A modal, inline edit, or wizard step needs to focus a known input the first time it renders.
**How it works:** Declare `lwc:ref="firstInput"` on the input. In `renderedCallback`, check a `_focused` boolean; if false, call `this.refs.firstInput?.focus()` and flip the flag. Reset the flag when the modal closes so the next open focuses again.
**Why not the alternative:** Calling `this.template.querySelector('.input')` relies on an unstable class name and races with the first render if called from `connectedCallback`.
### Imperative Form Validation Via Named Refs
**When to use:** The form has a small, known set of required fields and needs to call `reportValidity()` on each on submit.
**How it works:** Name each `lightning-input` with a distinct `lwc:ref`. On submit, iterate over the named refs (`['email', 'phone', 'amount']`) and call `reportValidity()` on each through `this.refs[name]`. Aggregate the boolean results.
**Why not the alternative:** `querySelectorAll('lightning-input')` sweeps in inputs that may not be required and hides intent; a named-ref list documents the contract in the template.
### Imperative Handle To A Single Child Component
**When to use:** A parent needs to call an `@api` method on exactly one child instance — for example `this.refs.chart.refresh()`.
**How it works:** Put `lwc:ref="chart"` on `<c-chart>` and invoke `this.refs.chart.refresh()` after render. The ref resolves to the child's host element, which exposes the `@api` surface.
**Why not the alternative:** A class-based lookup duplicates intent in CSS and breaks silently when the class is renamed by a refactor.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Need to focus or invoke a method on one known element | `lwc:ref="name"` + `this.refs.name` | Declarative, typed, survives refactors |
| Need to query N items rendered inside `for:each` | `data-*` + `event.target.closest('[data-*]')` or `template.querySelectorAll` | Refs explicitly do not work inside iterators |
| Need to inspect a child custom component's internal DOM | Do not — add an `@api` method or event to the child | Shadow boundary is intentional encapsulation |
| Element is inside `lwc:if` that may be false | Null-check `this.refs.x?.focus()` and access after the branch renders | Ref is undefined when the branch is not in the DOM |
| Code runs in `connectedCallback` | Move DOM work to `renderedCallback` with a one-shot flag | `this.refs` is not populated until after first render |
---
## Recommended Workflow
1. Identify each DOM access point in the component and categorize as single-element, list, conditional, or cross-shadow.
2. For single-element accesses on owned elements, add `lwc:ref="<name>"` with a meaningful, unique name and replace the selector call with `this.refs.<name>`.
3. Move any ref access out of `constructor` and `connectedCallback` into `renderedCallback` (or a user-event handler) and guard one-shot work with a boolean flag.
4. For list accesses or iterator children, keep `querySelector`/`querySelectorAll` or switch to `data-*` + event delegation — do not put `lwc:ref` inside `for:each`.
5. Run `scripts/check_lwc_template_refs.py` to catch refs inside `for:each`, refs used in `connectedCallback`, and duplicate names; fix findings and re-run.
---
## Review Checklist
- [ ] No `lwc:ref` appears inside a `for:each` or `iterator` template.
- [ ] No component accesses `this.refs` inside `constructor` or `connectedCallback`.
- [ ] Every `lwc:ref` name is unique within its template root.
- [ ] Refs that sit inside a `lwc:if` branch are accessed with optional chaining or an explicit guard.
- [ ] Migrated components no longer carry both `lwc:ref` and the legacy class-based `querySelector` for the same element.
- [ ] Cross-component interactions go through `@api` and events, not through reaching into a child's shadow DOM.
---
## Salesforce-Specific Gotchas
1. **`this.refs` is undefined in `connectedCallback`** — the lifecycle docs are explicit: refs are only populated after the first render pass, so code that assumes they exist on mount throws.
2. **Refs inside `for:each` are unsupported** — the docs forbid it because the name would repeat per iteration; use `data-*` attributes instead.
3. **A false `lwc:if` branch means no ref** — conditional rendering removes the element from the DOM, and `this.refs.<name>` becomes undefined until the branch renders again.
4. **Refs stop at the child component's shadow root** — you cannot reach into `<c-child>` to grab one of its internal nodes; expose the behavior with `@api` instead.
5. **`this.template.querySelector` is still valid and sometimes required** — for lists, complex selectors, or dynamic children, the legacy API is still the right tool; refs do not replace it universally.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| Refactored template/JS | Component pairs using `lwc:ref` with lifecycle-safe access |
| Migration notes | Summary of which selectors were replaced, which remain, and why |
| Checker report | Line-numbered findings for refs-in-iterators, refs-in-`connectedCallback`, and duplicate ref names |
---
## Related Skills
- `lwc/lwc-focus-management` — use when focus sequencing, accessibility, and keyboard navigation are the primary concerns, not just the ref mechanics.
- `lwc/lifecycle-hooks` — use when the underlying issue is lifecycle timing (`connectedCallback` vs `renderedCallback`) rather than DOM lookup style.
- `lwc/lwc-accessibility` — use when refs are being added specifically to drive ARIA state, screen-reader announcements, or focus traps.Related Skills
sandbox-refresh-and-templates
Sandbox refresh cycles, sandbox templates, post-refresh automation via the SandboxPostCopy Apex interface, and data handling during refresh. NOT for sandbox type selection (use sandbox-strategy).
pr-policy-templates
Enforce change quality via PR templates, required reviews, metadata ownership, and automated checks. NOT for branching model selection.
fsl-service-report-templates
Use this skill when designing, generating, or troubleshooting Field Service service report templates — covers the createServiceReport REST action (API v40.0+), ServiceReportLayout configuration, DigitalSignature capture, Document Builder (Winter '25+) with conditional logic via Flow, and PDF storage as ContentDocument/ContentVersion. NOT for quote templates, custom Visualforce pages, or Experience Cloud document generation.
prompt-template-versioning
Lifecycle management for Prompt Builder templates: version, test, promote, roll back via CMDT-backed bindings. NOT for authoring initial templates or generic prompt engineering.
prompt-builder-templates
Use when creating, reviewing, or troubleshooting Prompt Builder templates (Field Generation, Record Summary, Sales Email, or Flex types), including grounding with merge fields, Flow, or Apex. Trigger keywords: prompt template, Prompt Builder, field generation, record summary, sales email template, flex template, grounding, merge fields, LLM template, Einstein generative AI. NOT for agent topic instructions, Copilot action configuration, or Data Cloud segment activation.
quotes-and-quote-templates
Use when configuring standard Salesforce Quotes, building or customizing quote templates for PDF generation, emailing quotes to customers, syncing quotes to opportunity products, or setting up discount approval processes on quotes. Triggers: 'create quote', 'quote template', 'quote PDF', 'email quote', 'quote sync', 'synced quote', 'discount approval', 'quote line items'. NOT for CPQ (Salesforce Revenue Cloud / SBQQ) quote configuration, quote line scheduling, or order management.
lightning-bolt-template-authoring
Use when an admin or partner needs to package an Experience Cloud (Community) site as a reusable Lightning Bolt Solution for distribution — covers the export workflow from Experience Builder, what gets bundled (ExperienceBundle, custom apps, flow categories, theme, layouts, navigation menus) versus what does NOT (data, CMS content, files), choosing Bolt vs managed package vs unlocked package vs cloning a site, sandbox-to-production promotion, multi-org distribution, AppExchange listing as a Bolt, and template versioning via the LightningBolt metadata `versionNumber`. Triggers: 'turn this community into a reusable template', 'package an Experience Cloud site to ship to multiple orgs', 'export Experience Builder template for AppExchange', 'should we use a Bolt or a managed package for this community', 'create an industry-specific community starter', 'how do we version our partner portal template', 'distribute branded Experience site across business units'. NOT for general Experience Cloud site build, content, or member setup (use admin/experience-cloud-site-setup, admin/experience-cloud-cms-content, admin/experience-cloud-member-management). NOT for shipping Apex / LWC / data-model functionality as a product (use devops/managed-package-development, devops/second-generation-managed-packages, devops/unlocked-package-development). NOT for moving a single Experience site between sandbox and prod as a one-off (use admin/experience-cloud-deployment-admin, devops/cicd-for-experience-cloud).
email-templates-and-alerts
Use when designing, reviewing, or troubleshooting Salesforce email templates, email alerts, and declarative notification design. Triggers: 'Lightning Email Template', 'email alert', 'merge field', 'org-wide email', 'too many emails', 'mass email limit'. NOT for marketing automation or custom Apex email services.
cpq-quote-templates
Use when designing or troubleshooting Salesforce CPQ (SBQQ) quote templates: building template sections, configuring line columns, conditionally showing sections, generating branded PDFs, or handling multi-language output. Triggers: 'CPQ quote template', 'SBQQ template', 'CPQ PDF', 'line columns', 'quote template sections', 'conditional section', 'CPQ quote document'. NOT for standard Salesforce quote templates (Setup > Quote Templates), Visualforce-only PDF customization, or CPQ pricing rules and price books.
classic-email-template-migration
Migrating Classic email templates (Text, HTML with Letterhead, Custom HTML, Visualforce email) to Lightning Email Templates (LET) and the Email Template Builder. Covers merge-field translation, Letterhead-to-Enhanced-Letterhead conversion, Visualforce email retention strategy, folder reorganization, sender-context (OrgWideEmailAddress) preservation, and downstream Email Alert / Process Builder / Flow rewiring. NOT for transactional Apex email sending (use apex/apex-outbound-email-patterns) or marketing email broadcasts (use Marketing Cloud / Account Engagement).
xss-and-injection-prevention
Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).
visualforce-security-and-modernization
Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).