Lit
Lit helps you build web components using reactive properties and declarative templates. Start with a small public contract: what callers supply, what the element renders, and which events announce user actions. Keep internal state distinct from the values a host application owns.
A custom element is a browser integration boundary. Its name and HTML appearance do not establish accessibility, framework compatibility or a secure data flow. Test those behaviors in the actual host rather than assuming that one working component proves every integration.
TL;DR
- Declare reactive data and understand the timing of scheduled rendering.
- Distinguish string attributes from JavaScript properties and private component state.
- Use explicit event names, payloads and propagation rules for host communication.
- Treat shadow DOM as encapsulation rather than a security boundary.
- Verify keyboard use, lifecycle cleanup and representative framework integration.
Quick Example
Save this module as counter.js in a project with Lit installed and a bundler or import map resolving the lit package. Load it as a module and place <wiki-counter></wiki-counter> in the page. The plain JavaScript declaration avoids decorator-toolchain requirements.
The button starts at Count: 0; activating it increments the rendered count and emits count-changed with the new count. A host can listen with addEventListener on the element or an ancestor. The component and event were exercised in a local browser fixture. That check does not establish server rendering, every host framework or a complete accessibility audit.
Core Concepts
Reactive data schedules updates
Lit’s reactive properties trigger its update process. Public reactive properties normally correspond to observed attributes; internal reactive state does not. Attributes are strings, so conversion and reflection rules matter when exposing numbers, booleans or structured values. The example marks count as internal state. Reactive properties
Templates describe the intended DOM
The html template connects text and event bindings to component data. Lit’s event binding invokes the handler with component context. Keep rendering focused on the current state and place external effects in an appropriate lifecycle or event path. Events
Event propagation is part of the contract
bubbles lets an event travel through ancestors; composed permits it to cross shadow boundaries. Choose those settings according to the intended audience. The example dispatches on the host and also supports observation outside a containing shadow root. Do not expose sensitive internal data in a broadly propagating event. Shadow DOM events
Design a component a host can use
Write the contract before adding features. Name each public property, its type, default, accepted range, ownership and update behavior. Define each event’s payload and whether it describes an action, a state change or a request the host may reject.
For structured values, prefer an explicit JavaScript property contract rather than making callers guess a JSON attribute format. If the component is controlled by its host, define how it requests changes and waits for the authoritative value. Avoid an accidental loop where host reflection and component events repeatedly trigger each other.
Use native semantic controls when they match the interaction. The example’s native button provides a useful starting point for keyboard activation; it does not prove that a larger custom widget has correct labeling, focus order or assistive-technology behavior. Test those requirements independently.
Keep styles, lifecycle and trust explicit
Shadow-root styles help isolate component presentation. They also change how application styles reach internal elements. Define intended customization points instead of making host code depend on undocumented internal selectors or DOM structure.
Check the component in the actual themes and layouts it must support. Include text expansion, narrow containers, zoom and high-contrast requirements where applicable. A CSS rule that looks fine in an isolated demo can fail when the element is placed inside a dense form or an application with different typography.
External listeners, observers, timers and subscriptions need lifecycle ownership. Arrange connection and disconnection so removing and reattaching an element does not multiply callbacks. For asynchronous work, account for stale results and disconnected elements; a late response should not overwrite a newer request’s state.
Treat input as data and validate it at the appropriate trust boundary. A template helper is not a reason to insert untrusted raw HTML, construct executable content or expose credentials through attributes and events. Shadow DOM hides some implementation detail from ordinary styling and targeting; it is not an authorization or confidentiality mechanism.
Keep the example’s counter deliberately small. It has no network requests, global listeners or persistence, so it does not demonstrate cancellation, application state ownership or secret handling. Add those features only with an explicit contract and relevant failure cases.
Verify the host integration rather than its reputation
Check the actual framework and version where the element will be consumed. Confirm how the host sets properties, attaches custom-event listeners, renders initial markup and handles element registration. A framework wrapper may be useful when it provides a clearer contract, but validate its behavior rather than assuming it is required everywhere.
For a representative test, mount the component, observe its initial output, activate its control, assert the event payload and wait for the render to settle before inspecting the new text. Then unmount and remount it to catch lifecycle leaks. Test invalid inputs according to the declared policy.
Keep server rendering and hydration as separate acceptance work if they are required. Loading the same client module in a browser does not test server output, hydration mismatch or a host’s streaming behavior. Likewise, a passing local browser is not a compatibility inventory across all supported engines.
Choose Lit when its web-component model fits the required reuse and the team can maintain these contracts. Reconsider a component boundary if it forces excessive host synchronization, inaccessible custom controls or undocumented framework workarounds. The useful measure is a working integration that remains understandable to its maintainers.
Best Practices
Expose a small stable contract
Keep public inputs and events documented. Treat internal DOM and private state as implementation details that hosts should not depend on.
Observe completed updates
Wait for the component’s update boundary in tests before asserting rendered state. Verify the host-visible event separately from the final DOM.
Test the real interaction
Include keyboard, focus, removal/reconnection and the supported host framework. Add server behavior only when it has actually been exercised.
Common Mistakes
Bad: Expecting an attribute to carry an arbitrary JavaScript object.
Correct: Declare a property interface or an explicit conversion contract and test the real host assignment.
Bad: Assuming shadow DOM makes internal data secure.
Correct: Apply normal validation, authorization and confidentiality rules; encapsulation is not security isolation.
Bad: Testing only the rendered number.
Correct: Check action, event payload, propagation, update timing and the host’s response too.
FAQ
Do I need decorators?
No. Lit supports static property declarations, as the example shows. Choose syntax compatible with the project’s actual tooling.
Does count appear as an attribute here?
No. It is internal reactive state in this example. A public property/attribute contract would be a separate design choice.
Can I use the element from another framework?
Web components provide an integration boundary, but property, event, rendering and lifecycle behavior must be checked in that framework and version.
Related Topics
- Frontend Development — Place components within the application architecture.
- Web Audio — Add a browser capability behind an explicit UI action.
- Documentation Information Architecture — Document public component contracts where hosts can find them.