Salesforce · · 17 min read

An Introduction to HTML for LWC

HTML fundamentals for Lightning Web Components — what HTML is, the essential elements and attributes you need for building LWCs, and how to debug HTML in the browser console.

Part 74: An Introduction to HTML for LWC

Welcome back to the Salesforce blog series. In Part 72, we kicked off Topic 3 by exploring the basics of Lightning Web Components — what they are, when to use them, and how to create and deploy your first one. In Part 73, we took a step back and learned about the DOM — the Document Object Model — and how the browser takes your code and turns it into a living, interactive tree of elements. Now it is time to learn the language that actually defines those elements: HTML.

If you have spent your career writing Apex triggers and configuring page layouts, HTML might feel unfamiliar. That is completely fine. HTML is not a programming language — it is a markup language. It tells the browser what to display, not how to behave. Think of it as the skeleton of your component. JavaScript adds the brains, CSS adds the looks, but HTML provides the structure. Every LWC you build starts with an HTML template file, so understanding HTML is not optional — it is the foundation of everything we will do going forward.

In this post, we will cover what HTML is at a fundamental level, walk through the elements and attributes you will use most often when building LWCs, learn how to debug HTML using the browser console, and wrap up with a cheat sheet you can reference any time you are building a component.


What is HTML?

HTML stands for HyperText Markup Language. It was invented by Tim Berners-Lee in 1991 as a way to structure and link documents on the early internet. Every web page you have ever visited — Google, Salesforce, your company intranet — is built on HTML at its core.

HTML works by wrapping content in tags. Tags are special keywords surrounded by angle brackets that tell the browser what kind of content is inside them. For example, if you want to display a paragraph of text, you wrap it in <p> tags:

<p>This is a paragraph of text.</p>

The <p> is the opening tag, and the </p> is the closing tag. The content between them is what gets rendered in the browser. Not all tags require a closing tag — some are self-closing — but most follow this open-content-close pattern.

How HTML Relates to LWC

In a Lightning Web Component, every component has an HTML template file with the .html extension. This file lives alongside your JavaScript (.js) and metadata (.xml) files in the component folder. When Salesforce renders your component, it takes the HTML template, processes any dynamic expressions, and injects the result into the page DOM.

Here is what a minimal LWC HTML template looks like:

<template>
    <p>Hello, this is my first LWC.</p>
</template>

Notice the <template> tag wrapping everything. This is required in every LWC HTML file. The <template> tag tells the LWC framework that this is a component template, not a standalone HTML page. You never add <html>, <head>, or <body> tags in an LWC — the framework handles all of that for you. Your job is to define what goes inside the template.

The Anatomy of an HTML Element

Before we dive into specific elements, let us make sure the terminology is clear. An HTML element is made up of three parts:

  1. Opening tag — The element name wrapped in angle brackets. Example: <div>
  2. Content — Whatever goes between the opening and closing tags. This can be text, other elements, or nothing at all.
  3. Closing tag — The element name preceded by a forward slash, wrapped in angle brackets. Example: </div>

Together, these form a complete element:

<div>This is the content inside a div element.</div>

Tags can also have attributes, which provide additional information about the element. Attributes live inside the opening tag and follow a name="value" pattern:

<a href="https://salesforce.com" target="_blank">Visit Salesforce</a>

In this example, href and target are attributes of the <a> (anchor/link) element. The href tells the browser where the link should go, and target="_blank" tells it to open the link in a new tab.


The Basics of HTML for LWC’s

You do not need to know every HTML element to build LWCs. In practice, you will use a relatively small set of elements over and over again. Let us walk through the ones that matter most.

Container Elements

Container elements are used to group and organize other elements. They do not display anything on their own — they just provide structure.

The <div> element is the most common container in HTML. It is a generic block-level element that you use to wrap sections of your component:

<template>
    <div>
        <p>This paragraph is inside a div.</p>
        <p>So is this one.</p>
    </div>
</template>

The <span> element is similar to <div>, but it is inline rather than block-level. Use <span> when you want to wrap a piece of text inside a line without breaking the flow:

<template>
    <p>The status is <span class="highlight">Active</span> for this record.</p>
</template>

The <section> element is a semantic container that represents a thematic grouping of content. Use it instead of <div> when the grouped content represents a distinct section of your component:

<template>
    <section>
        <h2>Account Details</h2>
        <p>Name: Acme Corporation</p>
        <p>Industry: Manufacturing</p>
    </section>
</template>

Text Elements

Headings (<h1> through <h6>) define the hierarchy of your content. <h1> is the most important, <h6> is the least. In LWCs, you will typically start with <h2> or <h3> because the page layout often already provides an <h1>:

<template>
    <h2>Contact Information</h2>
    <h3>Primary Address</h3>
    <p>123 Main Street, San Francisco, CA 94105</p>
    <h3>Secondary Address</h3>
    <p>456 Oak Avenue, New York, NY 10001</p>
</template>

Paragraphs (<p>) are used for blocks of text. The bold (<strong>) and italic (<em>) tags let you add emphasis:

<template>
    <p>This account is <strong>past due</strong> and requires <em>immediate</em> attention.</p>
</template>

Lists

Lists come in two flavors. Unordered lists (<ul>) display bullet points, and ordered lists (<ol>) display numbered items. Each item inside is wrapped in an <li> (list item) tag:

<template>
    <h3>Open Opportunities</h3>
    <ul>
        <li>Acme Corp - Enterprise License</li>
        <li>Globex Inc - Support Package</li>
        <li>Initech - Custom Development</li>
    </ul>

    <h3>Steps to Close</h3>
    <ol>
        <li>Send the proposal</li>
        <li>Schedule the demo</li>
        <li>Negotiate pricing</li>
        <li>Get sign-off</li>
    </ol>
</template>

Lists are extremely common in LWCs. Anytime you are displaying a collection of records or a set of steps, you will reach for <ul> or <ol>.

Tables

Tables are used to display structured data in rows and columns. In LWC development, you might use tables for simple data displays, though for more complex scenarios you will often use the lightning-datatable component from the base library instead:

<template>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Industry</th>
                <th>Revenue</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Acme Corp</td>
                <td>Manufacturing</td>
                <td>$5,000,000</td>
            </tr>
            <tr>
                <td>Globex Inc</td>
                <td>Technology</td>
                <td>$12,000,000</td>
            </tr>
        </tbody>
    </table>
</template>

Here, <table> is the container. <thead> holds the header row, <tbody> holds the data rows. Each row is a <tr> (table row), header cells are <th> (table header), and data cells are <td> (table data).

Form Elements

Even though LWCs often use the Salesforce base components for forms (like lightning-input and lightning-combobox), understanding the underlying HTML form elements is important because those base components render native HTML form elements under the hood.

Input fields are created with the <input> tag. The type attribute determines what kind of input it is:

<template>
    <div>
        <label for="accountName">Account Name</label>
        <input type="text" id="accountName" placeholder="Enter account name" />
    </div>

    <div>
        <label for="revenue">Annual Revenue</label>
        <input type="number" id="revenue" placeholder="Enter revenue" />
    </div>

    <div>
        <label for="isActive">Active</label>
        <input type="checkbox" id="isActive" />
    </div>
</template>

Notice that <input> is a self-closing tag — it does not have a closing </input> tag. The forward slash before the closing angle bracket (/>) is a convention to indicate this.

The <label> element is paired with inputs using the for attribute, which matches the id of the input. This is important for accessibility — it tells screen readers which label belongs to which input.

Textareas are used for multi-line text input:

<template>
    <label for="description">Description</label>
    <textarea id="description" rows="4" placeholder="Enter a description"></textarea>
</template>

Select dropdowns let users choose from a list of options:

<template>
    <label for="industry">Industry</label>
    <select id="industry">
        <option value="">-- Select --</option>
        <option value="tech">Technology</option>
        <option value="finance">Finance</option>
        <option value="healthcare">Healthcare</option>
    </select>
</template>

Buttons

Buttons are one of the most common elements in any LWC. In plain HTML, a button looks like this:

<template>
    <button type="button" onclick={handleClick}>Save Record</button>
</template>

Notice the onclick={handleClick} attribute. This is where HTML meets JavaScript in LWC. The curly brace syntax {handleClick} is an LWC-specific expression that binds the button click to a JavaScript method in your component’s .js file. We will cover this in detail when we get to JavaScript and data binding in later sections.

In practice, you will usually use <lightning-button> instead of a plain <button>, but knowing the raw HTML version helps you understand what is happening underneath.

Anchor tags (<a>) create hyperlinks:

<template>
    <a href="https://help.salesforce.com" target="_blank" rel="noopener noreferrer">
        Salesforce Help Documentation
    </a>
</template>

The rel="noopener noreferrer" attribute is a security best practice when using target="_blank". It prevents the new page from being able to access your page’s window object.

Image tags (<img>) display images. They are self-closing:

<template>
    <img src={logoUrl} alt="Company Logo" width="200" height="100" />
</template>

The alt attribute provides alternative text for screen readers and is displayed if the image fails to load. Always include it — it is essential for accessibility.

Attributes You Will Use Constantly

Across all of these elements, certain attributes appear again and again in LWC development:

  • class — Assigns CSS classes for styling. Example: <div class="slds-card slds-p-around_medium">
  • id — A unique identifier for the element. Used for label association and JavaScript targeting.
  • data-* — Custom data attributes. In LWC, these are incredibly useful for passing data from the template to JavaScript event handlers. Example: <button data-record-id={record.Id} onclick={handleSelect}>Select</button>
  • title — Tooltip text that appears when a user hovers over the element.
  • style — Inline CSS styles. Use sparingly in LWC — CSS files are preferred.

The data-* attributes deserve special attention. In LWC, when you handle an event, you often need to know which element triggered it. Custom data attributes let you embed information directly on the element:

<template>
    <template for:each={accounts} for:item="account">
        <div key={account.Id} data-account-id={account.Id} onclick={handleAccountClick}>
            <p>{account.Name}</p>
        </div>
    </template>
</template>

In your JavaScript, you can then access the data attribute from the event:

handleAccountClick(event) {
    const accountId = event.currentTarget.dataset.accountId;
    console.log('Selected account:', accountId);
}

Notice that data-account-id in the HTML becomes dataset.accountId in JavaScript. The browser automatically converts the hyphenated attribute name to camelCase in the dataset property.


How to Debug HTML in the Browser Console

Knowing how to write HTML is only half the battle. You also need to know how to inspect and debug it when things do not look right. The browser developer tools are your best friend here.

Opening the Developer Tools

In Chrome (the standard for Salesforce development), you can open the developer tools in a few ways:

  • Press F12 on your keyboard
  • Press Ctrl + Shift + I (Windows) or Cmd + Option + I (Mac)
  • Right-click on any element on the page and select Inspect

The third option is the most useful because it takes you directly to the element you right-clicked on in the Elements panel.

The Elements Panel

The Elements panel shows the live DOM tree of the page. You can expand and collapse elements, see their attributes, and view the computed styles. When you hover over an element in the Elements panel, the browser highlights it on the page — and vice versa.

For LWC debugging, the Elements panel is essential because LWC components render inside shadow DOM boundaries. When you inspect an LWC, you will see something like this in the DOM tree:

<c-my-component>
    #shadow-root (open)
        <div class="slds-card">
            <h2>Account Details</h2>
            <p>Acme Corporation</p>
        </div>
</c-my-component>

The #shadow-root (open) indicates that the component’s internal HTML is encapsulated in a shadow DOM. This is important to understand because it affects how CSS and JavaScript can reach into the component.

Inspecting Element Attributes

Click on any element in the Elements panel to see its attributes. You can also edit attributes directly in the panel to test changes without modifying your source code. Double-click an attribute value to edit it, or right-click and choose Edit as HTML to make larger changes.

This is incredibly useful for testing. Want to see how your component looks with a different CSS class? Just edit the class attribute in the Elements panel and see the result instantly.

Using the Console to Query Elements

The Console panel lets you run JavaScript against the live page. You can use it to find and inspect HTML elements:

// Find an element by its tag name
document.querySelector('c-my-component');

// Find elements inside a shadow root
document.querySelector('c-my-component').shadowRoot.querySelector('div');

// Get all elements with a specific class
document.querySelector('c-my-component').shadowRoot.querySelectorAll('.slds-card');

// Check the text content of an element
document.querySelector('c-my-component').shadowRoot.querySelector('h2').textContent;

Because LWCs use shadow DOM, you cannot simply do document.querySelector('.my-class') and expect to find elements inside a component. You have to first select the component itself, then access its shadowRoot, and then query within that shadow root.

The $0 Shortcut

When you select an element in the Elements panel, the Console gives you a shortcut to reference it: $0. So if you click on a <div> inside your LWC in the Elements panel, you can immediately type $0 in the Console to interact with it:

// After selecting an element in the Elements panel
$0.textContent          // Get its text
$0.className            // Get its CSS classes
$0.getAttribute('data-record-id')  // Get a data attribute
$0.style.backgroundColor = 'red'   // Temporarily change its style

This is a fast way to experiment with elements without writing temporary code in your component files.

Common Debugging Scenarios

Problem: Your component renders but is blank. Open the Elements panel and check if your component tag exists in the DOM. If the tag is there but the shadow root is empty or missing expected elements, the issue is likely in your JavaScript (a missing property or a conditional that evaluates to false).

Problem: Your layout looks wrong. Right-click the element that looks off, inspect it, and check the Styles panel on the right side. The browser shows you exactly which CSS rules are applied, which are overridden, and what the computed box model looks like.

Problem: You cannot find your element. Remember the shadow DOM boundary. If document.querySelector is not finding your element, make sure you are querying through the shadow root.


Section Notes: HTML Element Cheat Sheet

Here is a quick-reference table of the HTML elements you will use most frequently in LWC development.

Container and Layout Elements

ElementPurposeExample
<template>Required root element for every LWC HTML file<template>...</template>
<div>Generic block-level container<div class="container">...</div>
<span>Generic inline container<span class="bold">text</span>
<section>Semantic section of content<section>...</section>
<header>Header area of a section<header>...</header>
<footer>Footer area of a section<footer>...</footer>
<article>Self-contained content block<article>...</article>
<nav>Navigation links container<nav>...</nav>

Text Elements

ElementPurposeExample
<h1> to <h6>Headings (hierarchy)<h2>Title</h2>
<p>Paragraph of text<p>Some text here.</p>
<strong>Bold / important text<strong>Important</strong>
<em>Italic / emphasized text<em>Note this</em>
<br />Line break (self-closing)Line one<br />Line two
<hr />Horizontal rule (self-closing)<hr />
<code>Inline code snippet<code>getId()</code>
<pre>Preformatted text block<pre> indented text</pre>

List Elements

ElementPurposeExample
<ul>Unordered (bulleted) list<ul><li>Item</li></ul>
<ol>Ordered (numbered) list<ol><li>Step 1</li></ol>
<li>List item<li>List entry</li>

Table Elements

ElementPurposeExample
<table>Table container<table>...</table>
<thead>Table header group<thead><tr>...</tr></thead>
<tbody>Table body group<tbody><tr>...</tr></tbody>
<tr>Table row<tr><td>Data</td></tr>
<th>Table header cell<th>Column Name</th>
<td>Table data cell<td>Cell value</td>

Form Elements

ElementPurposeExample
<input>Text, number, checkbox, etc.<input type="text" />
<textarea>Multi-line text input<textarea rows="3"></textarea>
<select>Dropdown menu<select><option>...</option></select>
<option>Dropdown option<option value="a">Option A</option>
<label>Label for a form element<label for="name">Name</label>
<button>Clickable button<button onclick={handleClick}>Go</button>
<form>Form container<form onsubmit={handleSubmit}>...</form>
ElementPurposeExample
<a>Hyperlink<a href="/path">Link</a>
<img>Image (self-closing)<img src={url} alt="desc" />
<slot>LWC composition placeholder<slot name="header"></slot>

Essential Attributes for LWC

AttributeUsed OnPurpose
classAny elementAssign CSS classes (especially SLDS)
idAny elementUnique identifier for label pairing and JS access
data-*Any elementCustom data for event handling (dataset)
keyElements in for:eachUnique key for list rendering performance
for:each<template>Iterate over a collection
for:item<template>Name the current iteration variable
lwc:ifAny elementConditionally render an element (true = render)
lwc:elseifAny elementElse-if conditional rendering
lwc:elseAny elementElse conditional rendering
onclickAny elementBind a click event to a JS handler
onchangeInputsBind a change event to a JS handler
valueInputsBind or set the input value
placeholderInputsPlaceholder text inside the field
disabledInputs, buttonsDisable the element
titleAny elementTooltip text on hover

LWC-Specific Template Syntax Recap

Here is a quick reminder of the LWC template expressions you will see inside HTML files:

<!-- Dynamic value binding -->
<p>{propertyName}</p>

<!-- Conditional rendering -->
<template lwc:if={isVisible}>
    <p>This shows when isVisible is true.</p>
</template>
<template lwc:else>
    <p>This shows when isVisible is false.</p>
</template>

<!-- List iteration -->
<template for:each={items} for:item="item">
    <p key={item.id}>{item.name}</p>
</template>

<!-- Event binding -->
<button onclick={handleClick}>Click Me</button>

<!-- Data attributes for event context -->
<div data-id={record.Id} onclick={handleSelect}>{record.Name}</div>

<!-- Slot for component composition -->
<slot name="footer"></slot>

That covers the HTML fundamentals you need for Lightning Web Components. You now know what HTML is, which elements and attributes you will reach for most often, how to debug HTML in the browser, and you have a cheat sheet to keep nearby. In the next post, we will move on to CSS — the language that turns your bare HTML skeleton into something that actually looks good. See you there.