This example demonstrates how to architect a modern custom HTML element with clear separation of concerns, maintainable component boundaries, and a unified loading flow using modern module patterns such as top-level await.
All components below are composed from the same four source files:
template.html — shadow markup and slot contractstyles.css — encapsulated component styleselement.js — class definition, lifecycle behavior, and TLA setupdefined.js — explicit registration side-effect entrypoint
They are registered under different names using the same imported class and demonstrate how to create components
with and without side effects on import, as well as how to use query parameters to customize component behavior.
The element makes use of new URL(import.meta.url) to resolve loading paths dynamically. Use
package.json exports to define the preferred API surface for consumers and whether the side-effect
entrypoint should be default.
<script type="module">
import WebComponent from './src/element.js'
WebComponent.register('no-side-effects') // Convenience API
</script>
This component is registered with a static tag name and demonstrates how to create a component without side effects on import. It requires two explicit steps to import the class and then register the custom element, but it provides a more predictable loading flow and better separation of concerns.
<script type="module">
import './src/defined.js' // Uses default name
</script>
This component is registered as a side effect of importing the module from the './defined.js' entrypoint.
<script type="module">
import './src/defined.js?name=dynamic-name'
</script>
This component is registered with a custom tag name via query parameter while using the side effect entrypoint.
<script type="module">
import 'https://unpkg.com/web-component-best-practices/defined.js?name=cdn-dynamic-name'
</script>
This component demonstrates how to use the side effect entrypoint with a CDN URL and dynamic name via query parameter.