This lesson walks through building a complete, working Lit component step by step: defining the class, adding a reactive property, rendering a template, and using the finished tag in HTML.
Building a Simple Greeting Component
The clearest way to learn Lit is to build one small, complete component end to end. This example accepts a name property, greets the user, and includes a button that increments a personal counter, exercising templates, properties, events, and styles together.
Follow along by creating a new file, pasting the class definition, registering the element, and then using the resulting tag directly in an HTML file.
Importing the module file is what registers the <greeting-counter> tag with the browser.
The name attribute in HTML sets the name property automatically, because it was declared reactive.
You can also set name from JavaScript: document.querySelector('greeting-counter').name = 'Grace';.
The component re-renders automatically any time name or clicks changes, from any source.
First Component Checklist
The steps every new Lit component follows, in order.
Step
What to Do
1. Import
import { LitElement, html, css } from 'lit';
2. Extend
class MyEl extends LitElement { ... }
3. Declare properties
@property() fields or static properties
4. Add styles
static styles = css...`
5. Render
render() { return html...; }
6. Register
@customElement('my-el') or customElements.define()
7. Use it
<my-el></my-el> anywhere in HTML
Using Private Class Fields for Handlers
The #handleClick method in the example above uses native JavaScript private fields (the # prefix). This is optional, but it's a good habit for internal event handlers and helper methods that should never be called from outside the component.
Private methods (#method()) can't be accessed or overridden from outside the class, unlike a conventional underscore-prefixed convention.
Public reactive properties (like name) remain your component's actual API surface.
Keeping handlers private makes it clear, at a glance, which parts of a component are implementation detail.
Verifying the Component Renders
After registering the element, open your browser's DevTools and inspect the <greeting-counter> element. You should see a #shadow-root node containing the <p> and <button> — confirmation that Lit rendered into an encapsulated Shadow DOM tree rather than directly into the light DOM.
Clicking the button should update the count text in place without recreating the paragraph or button elements.
Common Mistakes
Forgetting to import the component's module file, so customElements.define()/@customElement never runs and the browser treats the tag as an unknown element.
Declaring a field without @property() or @state() and expecting it to trigger re-renders.
Setting a boolean attribute like disabled="false" in HTML and expecting it to be falsy — HTML attributes are strings, covered in the properties/attributes lesson.
Writing DOM manipulation code inside render() instead of returning a declarative html template.
Key Takeaways
A complete Lit component needs an import, a class extending LitElement, reactive properties, a render() method, and registration.
@property() syncs with HTML attributes; @state() is for properties that are reactive but internal.
Once registered, a Lit component's tag can be used exactly like any built-in HTML element.
Inspecting the Shadow DOM in DevTools is the fastest way to confirm a component rendered correctly.
Pro Tip
Keep your very first few components deliberately tiny (one property, one event handler) until the full render-on-change cycle feels automatic. It's much easier to debug a five-line component than a fifty-line one when something doesn't update as expected.
You've built and used your first Lit component. Next, look closer at the LitElement base class itself and what it provides.